값이 의미가 없을 때 새 부울 필드가 널 참조보다 낫습니까?


39

예를 들어 MemberlastChangePasswordTime 이있는 클래스가 있다고 가정합니다 .

class Member{
  .
  .
  .
  constructor(){
    this.lastChangePasswordTime=null,
  }
}

일부 멤버는 비밀번호를 변경하지 않을 수 있으므로 lastChangePasswordTime이없는 경우 의미가 없습니다.

그러나 null이 악의적 인 경우 값에 의미가 없을 때 사용해야하는 것은 무엇입니까? https://softwareengineering.stackexchange.com/a/12836/248528 , 나는 의미없는 값을 나타내는 널 (null)을 사용하지 않아야합니다. 그래서 부울 플래그를 추가하려고합니다.

class Member{
  .
  .
  .
  constructor(){
    this.isPasswordChanged=false,
    this.lastChangePasswordTime=null,
  }
}

그러나 나는 그것이 다음과 같은 이유로 매우 쓸모 없다고 생각합니다.

  1. isPasswordChanged가 false 인 경우 lastChangePasswordTime은 null이어야하며 lastChangePasswordTime == null 확인은 isPasswordChanged가 false 확인과 거의 동일하므로 lastChangePasswordTime == null을 직접 확인하는 것이 좋습니다.

  2. 여기서 논리를 변경할 때 필드를 모두 업데이트 하는 것을 잊어 버릴 수 있습니다.

참고 : 사용자가 비밀번호를 변경하면 다음과 같이 시간을 기록합니다.

this.lastChangePasswordTime=Date.now();

여기에 추가 부울 필드가 null 참조보다 낫습니까?


51
이 문제는 언어마다 다릅니다. 좋은 솔루션을 구성하는 요소는 프로그래밍 언어가 제공하는 기능에 크게 좌우되기 때문입니다. 예를 들어 C ++ 17 또는 Scala에서는 std::optional또는을 사용 Option합니다. 다른 언어로, 당신은 적절한 메커니즘을 직접 구축해야 할 수도 있고, null더 관용적이기 때문에 실제로 또는 유사한 것에 의지 할 수도 있습니다 .
Christian Hackl

16
lastChangePasswordTime을 비밀번호 작성 시간으로 설정하지 않으려는 이유가 있습니까 (생성은 결국 모드입니다)?
크리스티안 H

@ChristianHackl 흠, 다른 "완벽한"솔루션이 있다는 데 동의하지만 별도의 부울을 사용하는 것이 일반적으로 null / nil 확인을 수행하는 것보다 더 나은 아이디어가 있지만 (주요한) 언어는 보이지 않습니다. 그래도 꽤 오랫동안 활동하지 않았으므로 C / C ++에 대해 완전히 확신하지 못합니다.
Frank Hopkins

@FrankHopkins : 예를 들어 C 또는 C ++와 같이 변수를 초기화 할 수없는 언어가 있습니다. lastChangePasswordTime초기화되지 않은 포인터 일 수 있으며 다른 것과 비교하면 정의되지 않은 동작입니다. 특히 현대 C ++ (포인터를 전혀 사용하지 않는 곳)에서 NULL/ nullptr대신 포인터를 초기화하지 않는 강력한 이유 는 아니지만 누가 아는가? 또 다른 예는 포인터가 없거나 포인터를 잘못 지원하는 언어 일 수 있습니다. (FORTRAN 77이 떠오른다 ...)
Christian Hackl

나는 이것을 위해 3 가지 경우를 가진 열거 형을 사용할 것입니다 :)
J. Doe

답변:


72

의미없는 결석이있는 null경우 의도적이고 신중하게 사용해야 하는 이유를 모르겠습니다 .

실수로 값을 참조하지 않도록 nullable 값을 둘러싸는 것이 목표라면 isPasswordChangednull 확인 결과를 반환하는 함수 또는 속성으로 값을 만드는 것이 좋습니다 .

class Member {
    DateTime lastChangePasswordTime = null;
    bool isPasswordChanged() { return lastChangePasswordTime != null; }

}

제 생각에는 다음과 같이하십시오.

  • 컨텍스트를 잃을 수있는 null-check보다 더 나은 코드 가독성을 제공합니다.
  • isPasswordChanged언급 한 가치 유지에 대해 실제로 걱정할 필요가 없습니다 .

데이터를 유지하는 방법 (아마도 데이터베이스에서)은 널이 보존되도록하는 역할을합니다.


29
오프닝 +1 null의 원턴 사용은 일반적으로 null이 예기치 않은 결과, 즉 소비자에게 의미가없는 경우에만 비 승인됩니다. null이 의미가 있으면 예상치 못한 결과가 아닙니다.
Flater

28
동일한 상태를 유지하는 두 개의 변수가 없기 때문에 조금 더 강하게 만들 것입니다. 실패합니다. null 값이 인스턴스 내부에서 의미가 있다고 가정하면 null 값을 숨기는 것이 좋습니다. 이 경우 나중에 1970-01-01은 암호가 변경되지 않았 음을 나타냅니다. 그런 다음 나머지 프로그램의 논리는 신경 쓰지 않아도됩니다.
벤트

3
isPasswordChangednull인지 확인하는 것을 잊어 버릴 수있는 것처럼 전화 를 잊어 버릴 수 있습니다. 나는 여기에 아무것도 얻지 못했다.
Gherman

9
@Gherman 소비자가 null이 의미가 있거나 존재하는 값을 확인해야한다는 개념을 얻지 못하면 어느 쪽이든 손실되지만 메소드를 사용하면 모든 사람이 왜 코드를 읽었는지 알 수 있습니다. 확인하고 값이 null이거나 존재하지 않을 가능성이 있습니다. 그렇지 않으면 단지 "왜 그렇지 않기 때문에"또는 이것이 개념의 일부인지 여부에 대해 null 검사를 추가 한 개발자인지 확실하지 않습니다. 물론, 하나는 알아낼 수 있지만, 한 가지 방법은 구현을 조사하는 데 필요한 다른 정보를 직접 얻는 것입니다.
Frank Hopkins

2
@FrankHopkins : 좋은 점이지만 동시성이 사용되는 경우 단일 변수를 검사하더라도 잠금이 필요할 수 있습니다.
Christian Hackl

63

nulls악하지 않다. 생각없이 사용하는 것입니다. 이것은 null정답이 정확한 경우입니다-날짜가 없습니다.

솔루션에서 더 많은 문제가 발생합니다. 날짜 설정의 의미는 무엇입니까 isPasswordChanged? null값을 명확하게 정의하고 모호하지 않은 의미를 가지며 다른 정보와 충돌 할 수없는 반면, 특별히 파악하고 처리해야하는 상충되는 정보의 사례를 만들었습니다 .

따라서 아니요. 솔루션이 더 좋지 않습니다. null값을 허용하는 것이 올바른 방법입니다. null상황에 상관없이 항상 악 하다고 주장하는 사람들 은 왜 null존재 하는지 이해하지 못합니다 .


17
역사적으로 nullTony Hoare는 1965 년에 수십억 달러의 실수를 저질렀 기 때문에 존재합니다. null"악"이라고 주장하는 사람들 은 물론이 주제를 지나치게 단순화하고 있지만, 현대 언어 나 프로그래밍 스타일이에서 벗어나 null대체 되는 것이 좋습니다. 전용 옵션 유형이 있습니다.
Christian Hackl

9
@ChristianHackl : 역사적 관심에서 벗어난 것-Hoare는 더 나은 대안이 무엇 일지 말 해주었습니까 (완전히 새로운 패러다임으로 바꾸지 않고)?
AnoE

5
@AnoE : 흥미로운 질문입니다. Hoare가 그 말에 대해 무엇을 말했는지는 모르지만 요즘 무료로 설계된 현대 프로그래밍 언어에서는 null-free 프로그래밍이 현실입니다.
Christian Hackl

10
@ 톰 왓? C # 및 Java와 같은 언어에서 DateTime필드는 포인터입니다. 전체 개념은 null포인터에만 의미가 있습니다!
토드 세웰

16
@Tom : Null 포인터는 언어와 구현에 대해서만 위험하므로, 아무리 큰 소리를 낼지라도 맹목적으로 색인을 생성하고 역 참조 할 수 있습니다. 트랩이 널 포인터를 색인화하거나 역 참조하려고 시도하는 언어에서는 종종 더미 오브젝트에 대한 포인터보다 덜 위험합니다. 프로그램이 비정상적으로 종료되는 것이 무의미한 숫자를 생성하는 것보다 선호되는 경우가 많으며,이를 포획하는 시스템에서는 널 포인터가 올바른 방법입니다.
supercat

29

프로그래밍 언어에 따라 optional데이터 유형 과 같은 좋은 대안이있을 수 있습니다 . C ++ (17)에서 이것은 std :: optional 입니다. 기본 데이터 유형이 없거나 임의의 값을 가질 수 있습니다.


8
거기에 Optional너무 자바; 널의 의미는 Optional... 당신까지 인
마티유 M.

1
옵션과 연결하기 위해 여기에 왔습니다. 나는 이것을 가지고있는 언어의 유형으로 이것을 인코딩했다.
Zipp

2
@FrankHopkins Java의 어떤 것도 당신이 글을 쓰지 못하게하는 것은 부끄러운 Optional<Date> lastPasswordChangeTime = null;일이지만 그로 인해 포기하지는 않을 것입니다. 그 대신, 선택적인 값을 할당 할 수없는 매우 단호한 팀 규칙을 도입하려고합니다 null. 옵션은 너무 많은 멋진 기능을 구입하여 쉽게 포기할 수 없습니다. 기본값 ( orElse또는 지연 평가 :) orElseGet, 오류 발생 ( orElseThrow), null 안전 방식으로 값 변환 ( map, flatmap) 등을 쉽게 지정할 수 있습니다 .
Alexander

5
@FrankHopkins Checking isPresent은 필자의 경험상 거의 항상 코드 냄새이며, 이러한 옵션을 사용하는 개발자가 "요점을 놓친 것"이라는 꽤 확실한 신호입니다. 실제로 기본적으로에 비해 이점이 ref == null없지만 자주 사용해야하는 것은 아닙니다. 팀이 불안정하더라도 정적 분석 도구는 대부분의 경우를 잡을 수 있는 linter 또는 FindBugs 와 같은 법률 을 적용 할 수 있습니다.
알렉산더

5
@FrankHopkins : Java 커뮤니티에는 약간의 패러다임 전환이 필요하지만 아직 몇 년이 걸릴 수 있습니다. 당신이 스칼라 보면, 예를 들어, 지원 null언어가 자바 100 % 호환이기 때문에, 그러나 아무도 그것을 사용하지 않으며, Option[T]같은 우수한 기능 프로그래밍 지원, 사방이다 map, flatMap간다 등등, 패턴 매칭과, 지금까지, 지금까지 이상으로 == null확인합니다. 이론 상으로는 옵션 유형이 영광스러운 포인터에 지나지 않는 것처럼 들리지만 현실은 그 주장이 잘못되었음을 증명합니다.
Christian Hackl

11

null을 올바르게 사용

사용 방법에는 여러 가지가 null있습니다. 가장 일반적이고 의미 적으로 올바른 방법은 단일 값 이 있거나 없을 때 사용하는 것 입니다. 이 경우 값 null은 데이터베이스의 레코드와 같은 의미이거나 의미가 있습니다.

이러한 상황에서는 대부분 의사 코드로 다음과 같이 사용합니다.

if (value is null) {
  doSomethingAboutIt();
  return;
}

doSomethingUseful(value);

문제

그리고 그것은 매우 큰 문제가 있습니다. 문제는 호출 할 doSomethingUseful때까지 값이 확인되지 않았을 수 있다는 것입니다 null! 그렇지 않은 경우 프로그램이 중단 될 수 있습니다. 그리고 사용자에게는 "끔찍한 오류 : 원하는 값이지만 null이 있습니다!"와 같은 오류 메시지가 표시되지 않을 수도 있습니다. (업데이트 후 :와 같은 정보 오류가 적 Segmentation fault. Core dumped.거나 더 나쁘지만 오류가 발생하지 않고 경우에 따라 null을 잘못 조작하는 경우도 있음)

점검 nullnull상황 처리 를 잊어 버리는 것은 매우 일반적인 버그 입니다. 발명 토니 호어는 이유는 null그가 1965 년 억 달러 실수를 한 것으로 2009 년에 QCon 런던라는 소프트웨어 회견에서 말했다 : https://www.infoq.com/presentations/Null-References-The-Billion-Dollar- 실수-토니-호 아르

문제 피하기

일부 기술과 언어 null는 다른 방법으로 잊을 수없는 검사를 하여 버그의 양을 줄입니다.

예를 들어 Haskell에는 Maybenull 대신 모나드 가 있습니다. DatabaseRecord이것이 사용자 정의 유형 이라고 가정하십시오 . Haskell에서 type 값은 Maybe DatabaseRecordJust <somevalue>거나 같을 수 있습니다 Nothing. 그런 다음 다른 방법으로 사용할 수 있지만 사용 방법에 Nothing관계없이 알지 못하면 일부 작업을 적용 할 수 없습니다 .

예를 들어이 함수가 호출 zeroAsDefault반환 x에 대한 Just x0를 들어 Nothing:

zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
    Nothing -> 0
    Just x -> x

Christian Hackl은 C ++ 17과 Scala는 각자의 방법이 있다고 말합니다. 따라서 언어에 그런 것이 있는지 찾아서 사용하려고 할 수 있습니다.

널은 여전히 ​​널리 사용됩니다

더 나은 것이 없다면 사용하는 것이 좋습니다 null. 계속 조심하십시오. 함수의 타입 선언은 어쨌든 도움이 될 것입니다.

또한 그다지 진보적이지는 않지만 동료가 사용하고 싶은지 null또는 다른 것을 확인해야합니다 . 이들은 보수적 일 수 있으며 몇 가지 이유로 새로운 데이터 구조를 사용하지 않을 수 있습니다. 예를 들어 언어의 이전 버전을 지원합니다. 이러한 것들은 프로젝트의 코딩 표준에 선언되어 팀과 적절히 논의되어야합니다.

당신의 제안에

별도의 부울 필드를 사용하는 것이 좋습니다. 그러나 어쨌든 확인해야하지만 여전히 확인을 잊어 버릴 수 있습니다. 그래서 여기서이긴 것은 없습니다. 매번 두 값을 업데이트하는 것과 같은 다른 것을 잊어 버릴 수 있다면 더 나쁩니다. 확인을 잊어 버리는 문제가 null해결되지 않으면 아무런 의미가 없습니다. 피하는 null것은 어려우므로 악화시키는 방식으로하지 않아야합니다.

null을 사용하지 않는 방법

마지막으로 null잘못 사용하는 일반적인 방법이 있습니다 . 이러한 방법 중 하나는 배열 및 문자열과 같은 빈 데이터 구조 대신 사용하는 것입니다. 빈 배열은 다른 배열과 마찬가지로 올바른 배열입니다! 여러 값에 맞을 수 있고 비어있을 수있는, 즉 길이가 0 인 데이터 구조에는 거의 항상 중요하고 유용합니다.

대수 관점에서 문자열의 빈 문자열은 숫자의 경우 0과 매우 비슷합니다.

a+0=a
concat(str, '')=str

: 빈 문자열은 일반적으로 문자열은 모노 이드 될 수 있습니다 https://en.wikipedia.org/wiki/Monoid 당신이 당신을 위해 그 중요하지하지 않는 경우.

이제이 예제를 사용하여 프로그래밍하는 것이 왜 중요한지 살펴 보겠습니다.

for (element in array) {
  doSomething(element);
}

빈 배열을 여기에 전달하면 코드가 정상적으로 작동합니다. 아무것도하지 않을 것입니다. 그러나 null여기에 전달하면 "null을 반복 할 수 없습니다. 죄송합니다"와 같은 오류로 충돌이 발생합니다. 우리는 그것을 감쌀 수는 if있지만 덜 깨끗하고 다시 확인해야합니다.

null 처리 방법

어떤 doSomethingAboutIt()일을하고, 특히이 예외를 throw할지 여부를해야 또 다른 복잡한 문제입니다. 간단히 말해 null주어진 작업에 허용되는 입력 값 인지 여부 와 응답시 예상되는 값에 따라 다릅니다 . 예상하지 못한 이벤트는 예외입니다. 그 주제에 대해서는 더 이상 다루지 않겠습니다. 이 답변은 이미 오래되었습니다.


5
실제로, horrible error: wanted value but got null!더 일반적인보다 훨씬 낫다 Segmentation fault. Core dumped....
Toby Speight

2
@TobySpeight True이지만 사용자의 용어 안에있는 것을 선호합니다 Error: the product you want to buy is out of stock.
Gherman

물론-나는 그것이 훨씬 더 나빠질 수 있음을 관찰하고 있었다 . 나는 더 나은 것에 완전히 동의합니다! 그리고 당신의 대답은 제가이 질문에 대해 말한 것입니다 : +1.
Toby Speight

2
@Gherman : 허용되지 않는 null곳을 지나가는 null것은 프로그래밍 오류, 즉 코드의 버그입니다. 재고가없는 제품은 일반적인 비즈니스 로직의 일부이며 버그가 아닙니다. 버그 탐지를 사용자 인터페이스의 일반 메시지로 변환 할 수 없으며 시도해서는 안됩니다. 특히 응용 프로그램이 중요한 응용 프로그램 (예 : 실제 금융 거래를 수행하는 응용 프로그램) 인 경우 즉시 충돌하고 더 많은 코드를 실행하지 않는 것이 선호되는 작업 과정입니다.
Christian Hackl

1
@EricDuminil 우리는 실수를 저지를 가능성을 제거 (또는 감소)하고 싶습니다 null.
Gherman

4

이전에 제공된 모든 훌륭한 답변 외에도 필드를 null로 만들려고 할 때마다 목록 유형 일지 신중하게 생각하십시오. 널 입력 가능 유형은 0 또는 1 요소의 목록과 동일하며 종종 N 요소 목록으로 일반화 될 수 있습니다. 이 경우 특히 lastChangePasswordTime의 목록 인 것을 고려할 수 있습니다 passwordChangeTimes.


훌륭한 지적입니다. 옵션 유형에 대해서도 마찬가지입니다. 옵션은 시퀀스의 특수한 경우로 볼 수 있습니다.
Christian Hackl

2

lastChangePasswordTime 필드가 필요한 동작은 무엇입니까?

IsPasswordExpired () 메소드에 해당 필드가 필요할 때마다 멤버에게 비밀번호를 자주 변경하도록 프롬프트를 표시해야하는지 여부를 판별하려면 해당 필드를 멤버가 처음 작성된 시간으로 설정하십시오. IsPasswordExpired () 구현은 신규 및 기존 구성원에 대해 동일합니다.

class Member{
   private DateTime lastChangePasswordTime;

   public Member(DateTime lastChangePasswordTime) {
      // set value, maybe check for null
   }

   public bool IsPasswordExpired() {
      DateTime limit = DateTime.Now.AddMonths(-3);
      return lastChangePasswordTime < limit;
   }
}

새로 작성된 멤버 비밀번호를 업데이트 해야 한다는 별도의 요구 사항이있는 경우 passwordShouldBeChanged라는 분리 된 부울 필드를 추가하고 작성시 true로 설정합니다. 그런 다음 해당 필드에 대한 검사를 포함하도록 IsPasswordExpired () 메서드의 기능을 변경하고 메서드의 이름을 ShouldChangePassword로 바꿉니다.

class Member{
   private DateTime lastChangePasswordTime;
   private bool passwordShouldBeChanged;

   public Member(DateTime lastChangePasswordTime, bool passwordShouldBeChanged) {
      // set values, maybe check for nulls
   }

   public bool ShouldChangePassword() {
      return PasswordExpired(lastChangePasswordTime) || passwordShouldBeChanged;
   }

   private static bool PasswordExpired(DateTime lastChangePasswordTime) {
      DateTime limit = DateTime.Now.AddMonths(-3);
      return lastChangePasswordTime < limit;
   }
}

코드에 의도를 명시하십시오.


2

첫째, 악의적 인 null은 교리이며 교리와 마찬가지로 통행 / 통과 테스트가 아닌 가이드 라인으로 가장 잘 작동합니다.

둘째, 값이 null이 될 수없는 방식으로 상황을 재정의 할 수 있습니다. InititialPasswordChanged는 초기에 false로 설정된 부울이고 PasswordSetTime은 현재 비밀번호가 설정된 날짜 및 시간입니다.

약간의 비용이 듭니다. 이제 비밀번호를 마지막으로 설정 한 이후의 시간을 항상 계산할 수 있습니다.


1

발신자가 사용하기 전에 확인하면 둘 다 '안전 / 정상 / 정확'입니다. 발신자가 확인하지 않으면 문제가 발생합니다. 어떤 것이 더 좋으며, 일부 널 오류가 있거나 유효하지 않은 값을 사용합니까?

정답은 하나도 없습니다. 그것은 당신이 걱정하는 것에 달려 있습니다.

충돌이 실제로 나쁘지만 대답이 중요하지 않거나 기본값이 허용되는 경우 부울을 플래그로 사용하는 것이 좋습니다. 잘못된 대답을 사용하는 것이 충돌보다 더 나쁜 문제이면 null을 사용하는 것이 좋습니다.

대부분의 '일반적인'사례의 경우 빠른 실패와 호출자 확인을 강요하는 것이 코드를 해독하는 가장 빠른 방법이므로 null이 기본 선택이어야한다고 생각합니다. 나는 "X는 모든 악의 근원"전도자들에게 너무 많은 믿음을 두지 않을 것입니다. 그들은 일반적으로 모든 사용 사례를 예상하지는 않았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.