불변 유형의 단점은 무엇입니까?


12

클래스의 인스턴스가 변경 될 것으로 예상되지 않을 때 더 많은 불변 유형을 사용 하고 있습니다 . 더 많은 작업이 필요하지만 (아래 예 참조) 멀티 스레드 환경에서 유형을보다 쉽게 ​​사용할 수 있습니다.

동시에 변경 가능성이 다른 사람에게 도움이되지 않더라도 다른 응용 프로그램에서는 변경 불가능한 유형을 거의 볼 수 없습니다.

질문 : 왜 다른 응용 프로그램에서 불변 유형이 거의 사용되지 않습니까?

  • 불변 유형의 코드를 작성하는 것이 더 길기 때문입니다.
  • 아니면 뭔가 빠졌고 불변 유형을 사용할 때 몇 가지 중요한 단점이 있습니까?

실생활의 예

다음 Weather과 같은 RESTful API에서 얻는다고 가정 해 봅시다 .

public Weather FindWeather(string city)
{
    // TODO: Load the JSON response from the RESTful API and translate it into an instance
    // of the Weather class.
}

우리가 일반적으로 보는 것은 (코드를 줄이기 위해 줄 바꿈과 주석이 제거 된 것입니다) :

public sealed class Weather
{
    public City CorrespondingCity { get; set; }
    public SkyState Sky { get; set; } // Example: SkyState.Clouds, SkyState.HeavySnow, etc.
    public int PrecipitationRisk { get; set; }
    public int Temperature { get; set; }
}

반면에 WeatherAPI에서 가져온 다음 수정하는 것이 이상 할 것 입니다. 실제 세계의 날씨를 변경 Temperature하거나 Sky변경 CorrespondingCity하지 않으며 변경 도 의미가 없습니다.

public sealed class Weather
{
    private readonly City correspondingCity;
    private readonly SkyState sky;
    private readonly int precipitationRisk;
    private readonly int temperature;

    public Weather(City correspondingCity, SkyState sky, int precipitationRisk,
        int temperature)
    {
        this.correspondingCity = correspondingCity;
        this.sky = sky;
        this.precipitationRisk = precipitationRisk;
        this.temperature = temperature;
    }

    public City CorrespondingCity { get { return this.correspondingCity; } }
    public SkyState Sky { get { return this.sky; } }
    public int PrecipitationRisk { get { return this.precipitationRisk; } }
    public int Temperature { get { return this.temperature; } }
}

3
"더 많은 작업이 필요합니다"– 인용이 필요합니다. 내 경험상 작업 이 필요 합니다.
Konrad Rudolph

1
@ KononRudolph : 더 많은 작업 으로 불변의 클래스를 만들기 위해 더 많은 코드를 작성해야합니다. 내 질문의 예는 이것을 변경할 수 있으며 클래스는 7 줄, 변경할 수없는 클래스는 19 줄입니다.
Arseni Mourzenko 2016 년

Visual Studio에서 코드 조각을 사용하는 경우 코드 조각 기능을 사용하여 코드 입력을 줄일 수 있습니다. 사용자 정의 스 니펫을 작성하고 IDE에서 몇 개의 키를 사용하여 필드와 특성을 동시에 정의하게 할 수 있습니다. 불변 유형은 멀티 스레딩에 필수적이며 스칼라와 같은 언어에서 광범위하게 사용됩니다.
Mert Akcakaya 2016 년

@Mert : 코드 스 니펫은 간단한 일에 좋습니다. 필드와 속성에 대한 주석과 올바른 순서로 전체 클래스를 작성하는 코드 스 니펫을 작성하는 것은 쉬운 일이 아닙니다.
Arseni Mourzenko 2016 년

5
나는 주어진 예에 동의하지 않습니다. 불변의 버전은 점점 더 많은 일을하고 있습니다. accessors를 사용하여 속성을 선언하여 인스턴스 수준 변수를 제거 할 수 {get; private set;}있으며 변경 가능한 변수 도 생성자가 있어야합니다. 모든 필드가 항상 설정되어야하고 왜 강제하지 않습니까? 이 두 가지를 합리적으로 적절히 변경하면 기능과 LoC 패리티가됩니다.
Phoshi

답변:


16

C # 및 Objective-C로 프로그래밍합니다. 나는 불변의 타이핑을 정말로 좋아하지만 실제로는 다음과 같은 이유로 항상 데이터 유형에 대한 사용을 제한해야했습니다.

  1. 변경 가능한 유형과 비교 한 구현 노력 . 불변 유형의 경우 모든 속성에 인수가 필요한 생성자가 있어야합니다. 당신의 모범은 좋은 것입니다. 각각 5-10 개의 속성을 가진 10 개의 클래스가 있다고 상상해보십시오. 작업을보다 쉽게하기 위해 C # 과 유사 StringBuilder하거나 UriBuilderC # 또는 WeatherBuilder귀하의 경우에 수정 된 불변 인스턴스를 생성하거나 작성하는 빌더 클래스가 필요할 수 있습니다 . 이것이 내가 디자인 한 많은 수업이 그렇게 노력할 가치가 없기 때문에 이것이 주된 이유입니다.
  2. 소비자 유용성 . 불변 유형은 변경 유형과 비교하여 사용하기가 더 어렵습니다. 인스턴스화는 모든 값을 초기화해야합니다. 불변성은 빌더를 사용하지 않고 인스턴스를 메소드에 전달하여 값을 수정할 수 없으며 빌더가 필요한 경우 단점은 내 (1)에 있음을 의미합니다.
  3. 언어 프레임 워크와의 호환성 많은 데이터 프레임 워크에는 변경 가능한 유형과 기본 생성자가 작동해야합니다. 예를 들어, 불변 유형으로 중첩 된 LINQ-to-SQL 쿼리를 수행 할 수 없으며 Windows Forms의 TextBox와 같은 편집기에서 편집 할 속성을 바인딩 할 수 없습니다.

요컨대, 불변성은 값처럼 동작하거나 속성이 거의없는 객체에 좋습니다. 불변을 만들기 전에, 불변으로 만든 후 필요한 노력과 클래스 자체의 유용성을 고려해야합니다.


11
"인스턴스화에는 미리 모든 값이 필요합니다.": 또한 초기화되지 않은 필드가 떠 다니는 객체를 가질 위험이 없다면
Giorgio

@Giorgio 변경 가능한 유형의 경우 기본 생성자는 인스턴스를 기본 상태로 초기화해야하며 인스턴스화 후 나중에 인스턴스 상태를 변경할 수 있습니다.
tia

8
불변 유형의 경우 동일한 기본 생성자를 가질 수 있으며 나중에 다른 생성자를 사용하여 복사 할 수 있습니다. 기본값이 변경 가능한 유형에 유효한 경우, 두 경우 모두 동일한 엔티티를 모델링하기 때문에 변경 불가능한 유형에도 유효해야합니다. 아니면 차이점은 무엇입니까?
Giorgio

1
고려해야 할 또 다른 사항은 유형이 나타내는 것입니다. 데이터 계약은 이러한 모든 점 때문에 좋은 불변 유형을 만들지 않지만 종속성으로 초기화되거나 데이터를 읽은 다음 작업을 수행하는 서비스 유형은 불변성에 좋습니다. 작업이 일관되게 수행되고 서비스 상태가 불가능하기 때문입니다. 위험에 빠졌다.
케빈

1
나는 현재 불변성이 기본값 인 F #으로 코딩하므로 구현하기가 더 쉽습니다. 포인트 3이 큰 장애물이라는 것을 알았습니다. 대부분의 표준 .Net 라이브러리를 사용하자마자 후프를 뛰어 넘어야합니다. (그리고 만약 그들이 반사를 사용해서 불변성을 우회한다면 ... argh!)
Guran

5

불변성과 관련이없는 언어로 생성 된 불변 유형은 일반적으로 개발자가 생성하는 데 더 많은 시간이 소요되고 원하는 변경을 표현하기 위해 일부 "빌더"유형의 객체가 필요한 경우 잠재적으로 사용하는 경향이 있습니다. 작업은 더 많을 것이지만,이 경우 선불 비용이 발생합니다). 또한 언어로 인해 변경 불가능한 유형을 쉽게 만들 수 있는지 여부에 관계없이 사소하지 않은 데이터 유형에 대해 항상 약간의 처리 및 메모리 오버 헤드가 필요한 경향이 있습니다.

부작용없이 기능 만들기

불변성을 중심으로하지 않는 언어로 작업하는 경우 실용적인 접근법은 모든 단일 데이터 유형을 불변으로 만들려고하지는 않는다고 생각합니다. 잠재적으로 훨씬 더 생산적인 사고 방식을 통해 동일한 이점을 얻을 수있는 것은 시스템에서 부작용없이 시스템의 기능을 극대화하는 데 집중하는 입니다.

간단한 예로, 다음과 같은 부작용을 일으키는 함수가있는 경우 :

// Make 'x' the absolute value of itself.
void make_abs(int& x);

그런 다음 초기화 후 할당과 같은 연산자가 부작용을 피하게하는 것을 금지하는 불변 정수 데이터 유형이 필요하지 않습니다. 우리는 간단히 이렇게 할 수 있습니다 :

// Returns the absolute value of 'x'.
int abs(int x);

이제 함수가 x범위를 벗어나거나 엉망이되지 않으므로이 사소한 경우 간접 / 앨리어싱과 관련된 오버 헤드를 피하여 일부주기를 단축했을 수도 있습니다. 최소한 두 번째 버전은 첫 번째 버전보다 계산 비용이 많이 들지 않아야합니다.

전체를 복사하는 데 비싼 것들

물론 함수가 부작용을 일으키는 것을 피하고 싶다면 대부분의 경우는 그리 쉬운 일이 아닙니다. 복잡한 실제 사용 사례는 다음과 같습니다.

// Transforms the vertices of the specified mesh by
// the specified transformation matrix.
void transform(Mesh& mesh, Matrix4f matrix);

이 시점에서 메시에는 십만 개 이상의 다각형, 더 많은 정점 및 모서리, 다중 텍스처 맵, 모프 타겟 등이있는 수백 메가 바이트의 메모리가 필요할 수 있습니다. transform다음과 같이 부작용이없는 기능 :

// Returns a new version of the mesh whose vertices been 
// transformed by the specified transformation matrix.
Mesh transform(Mesh mesh, Matrix4f matrix);

그리고이 경우 전체를 복사하는 것이 일반적으로 서사적 인 오버 헤드가되는 경우가 많습니다 Mesh. 영구적 인 데이터 구조와 아날로그 "builder"를 사용하여 수정 된 버전을 만들기 위해 불변의 유형으로 바꾸는 것이 유용하다는 것을 알았 습니다. 고유하지 않은 얕은 카피 및 참조 카운트 부품 부작용이없는 메쉬 함수를 작성하는 데 중점을두고 있습니다.

지속적인 데이터 구조

그리고 모든 것을 복사하는 것이 엄청나게 비싼 경우에, 나는 Mesh단지 스레드 안전성을 단순화하지 않았기 때문에 약간 가파른 비용이 있었음에도 불구하고 실제로 지불 할 불변의 디자인 노력을 발견 했습니다. 또한 비파괴 편집을 단순화하여 (사용자가 원래 사본을 수정하지 않고 메시 작업을 레이어 할 수 있음) 시스템 실행 취소 (현재 실행 취소 시스템은 메모리를 터뜨리지 않고 조작으로 변경하기 전에 변경 불가능한 메시 사본을 저장할 수 있음) 사용) 및 예외 안전 (이제 위의 함수에서 예외가 발생하는 경우 함수는 모든 부작용을 시작하지 않아도 롤백하고 모든 부작용을 취소 할 필요가 없습니다).

이러한 경우에 이러한 거대한 데이터 구조를 변경 불가능하게 만드는 데 필요한 시간이 비용보다 더 많은 시간을 절약했다고 ​​말할 수 있습니다. 이러한 새로운 디자인의 유지 관리 비용을 변경 가능성과 부작용을 일으키는 기능과 관련된 이전 디자인과 유지 관리 비용을 비교했기 때문에, 이전의 변경 가능한 디자인은 훨씬 더 많은 시간과 비용, 특히 예외 안전과 같이 개발자가 위기 시간 동안 방치하기를 유혹하는 영역에서 인적 오류가 발생하기 쉽습니다.

따라서 나는이 경우 불변의 데이터 유형이 실제로 돈을 지불한다고 생각하지만 시스템의 대부분의 기능을 부작용없이 만들기 위해 모든 것을 불변으로 만들 필요는 없습니다. 많은 것들이 전체를 복사하기에 충분히 싸다. 또한 많은 실제 응용 프로그램이 여기 저기에 부작용을 일으킬 필요가 있지만 (파일을 저장하는 것과 같이) 부작용이없는 훨씬 더 많은 기능이 있습니다.

나에게 불변의 데이터 유형을 갖는 요점은 작은 부분 만 있으면 대량의 데이터 구조를 왼쪽과 오른쪽으로 완전히 복사하는 형태로 서사시 오버 헤드를 발생시키지 않으면 서 부작용이 없도록 최대 함수 수를 작성할 수 있다는 것입니다. 그들 중 하나를 수정해야합니다. 이러한 경우 주변에 지속적인 데이터 구조가 있으면 최적화 세부 사항이되어 결과적으로 엄청난 비용을 들이지 않고 부작용없이 함수를 작성할 수 있습니다.

불변의 오버 헤드

이제 개념적으로 변경 가능한 버전은 항상 효율성면에서 우수합니다. 불변 데이터 구조와 관련된 계산 오버 헤드가 항상 있습니다. 그러나 위에서 설명한 경우에 가치있는 교환을 발견했으며 실제로 오버 헤드를 최소화하는 데 집중할 수 있습니다. 나는 최적화가 쉬워 지지만 정확성이 어려워지기보다는 정확성이 쉬워지고 최적화가 어려워지는 접근 방식을 선호합니다. 처음에는 올바르게 작동하지 않는 코드에 대해 더 정확하게 조정해야하는 코드가 완벽하게 올바르게 작동하여 잘못된 결과를 얼마나 빨리 얻는 지에 관계없이 크게 위축되지는 않습니다.


3

내가 생각할 수있는 유일한 단점은 이론 상으로는 변경 불가능한 데이터의 사용이 변경 가능한 것보다 느릴 수 있다는 것입니다. 새 인스턴스를 만들고 기존 인스턴스를 수정하는 것보다 이전 인스턴스를 수집하는 것이 느립니다.

다른 "문제"는 불변 유형 만 사용할 수 없다는 것입니다. 결국 상태를 설명해야하고 변경 가능 유형을 사용해야합니다. 상태를 변경하지 않으면 아무 작업도 할 수 없습니다.

그러나 여전히 일반적인 규칙은 가능한 한 불변의 유형을 사용하고 실제로 그렇게 할 이유가있을 때만 유형을 변경하는 것입니다 ...

그리고 " 불변 타입이 왜 다른 응용 프로그램에서 그렇게 드물게 사용 되는가? " 라는 질문에 대답하기 위해 -나는 정말로 그들이 생각하지는 않습니다 ... 모든 사람들이 클래스를 불변으로 만드는 것이 좋습니다 ... 예를 들면 : http://www.javapractices.com/topic/TopicAction.do?Id=29


1
두 문제 모두 Haskell에 없습니다.
Florian Margaine

@FlorianMargaine 좀 더 자세히 설명해 주시겠습니까?
mrpyo

스마트 컴파일러 덕분에 속도가 느려지는 것은 아닙니다. 그리고 Haskell에서는 I / O조차도 불변 API를 통해 이루어집니다.
Florian Margaine

2
속도보다 더 근본적인 문제는 불변의 객체가 상태가 변하는 동안 동일성을 유지하는 것이 어렵다는 것입니다. 가변 Car객체가 특정 물리적 자동차의 위치로 지속적으로 업데이트되면 해당 객체에 대한 참조가 있으면 해당 자동차의 위치를 ​​빠르고 쉽게 찾을 수 있습니다. Car불변 인 경우 현재 위치를 찾는 것이 훨씬 어려울 수 있습니다.
supercat

컴파일러가 이전 객체에 대한 참조가 남아 있지 않아이를 수정하거나 삼림 벌채 변환 등을 수행 할 수 있음을 알아 내기 위해 때로는 똑똑하게 코딩해야합니다. 특히 큰 프로그램에서. 그리고 @supercat이 말했듯이, 정체성은 실제로 문제가 될 수 있습니다.
Macke

0

상황이 변할 수있는 실제 시스템을 모델링하려면 변경 가능한 상태를 어딘가에 인코딩해야합니다. 객체가 변경 가능한 상태를 유지할 수있는 세 가지 주요 방법이 있습니다.

  • 변경 불가능한 객체에 대한 변경 가능한 참조 사용
  • 변경 가능한 객체에 대한 불변 참조 사용
  • 가변 객체에 대한 가변 참조 사용

먼저 사용하면 객체가 현재 상태의 변경 불가능한 스냅 샷을 쉽게 만들 수 있습니다. 두 번째를 사용하면 객체가 현재 상태의 라이브 뷰를 쉽게 만들 수 있습니다. 불변의 스냅 샷이나 라이브 뷰가 거의 필요하지 않은 경우 세 번째를 사용하면 특정 작업을보다 효율적으로 수행 할 수 있습니다.

변경 불가능한 객체에 대한 변경 가능한 참조를 사용하여 저장된 업데이트 상태가 변경 가능한 객체를 사용하여 저장된 상태를 업데이트하는 것보다 느리다는 사실 외에도, 변경 가능한 참조를 사용하면 상태의 저렴한 라이브 뷰를 구성 할 가능성을 포기해야합니다. 라이브 뷰를 만들 필요가 없다면 문제가되지 않습니다. 그러나 라이브 뷰를 만들어야하는 경우 변경 불가능한 참조를 사용할 수 없으면 뷰와 관련된 모든 작업 ( 읽기 및 쓰기)이 수행됩니다.-그렇지 않을 때보 다 훨씬 느립니다. 변경 불가능한 스냅 샷의 필요성이 라이브 뷰의 필요를 초과하는 경우, 변경 불가능한 스냅 샷의 향상된 성능으로 인해 라이브 뷰의 성능 저하가 정당화 될 수 있지만 라이브 뷰가 필요하고 스냅 샷이 필요없는 경우 변경 가능한 오브젝트에 대한 불변 참조를 사용하는 것이 좋습니다 토고.


0

귀하의 경우 대답은 주로 C #이 불변성에 대한 지원이 부족하기 때문입니다 ...

다음과 같은 경우에 좋을 것입니다.

  • 달리 언급하지 않는 한 (예 : 'mutable'키워드를 사용하여) 불변 및 가변 유형을 혼합하는 것이 혼란스럽지 않으면 모든 것이 기본적으로 불변입니다.

  • 방법 (돌연변이은 With) 자동으로 사용할 수 있습니다 -이 이미 볼을 달성 할 수 있지만 함께

  • 특정 메소드 호출의 결과를 말하는 방법이있을 것입니다 (예 ImmutableList<T>.Add:). 버릴 수 없거나 최소한 경고가 발생합니다

  • 그리고 주로 컴파일러가 요청한 경우 불변성을 보장 할 수있는 경우 ( https://github.com/dotnet/roslyn/issues/159 참조 )


1
세 번째 요점으로, ReSharper는 MustUseReturnValueAttribute이를 정확하게 수행 하는 사용자 정의 속성을 가지고 있습니다. PureAttribute동일한 효과가 있으며 더 좋습니다.
Sebastian Redl

-1

다른 응용 프로그램에서 불변 유형이 거의 사용되지 않는 이유는 무엇입니까?

무지? 무경험?

불변의 객체는 오늘날 우수한 것으로 널리 알려져 있지만 비교적 최근의 개발입니다. 최신 상태를 유지하지 않거나 단순히 '알고있는 것'에 갇힌 엔지니어는 사용하지 않습니다. 그리고 효과적으로 사용하려면 약간의 디자인 변경이 필요합니다. 앱이 오래되었거나 엔지니어가 설계 기술이 약한 경우 앱을 사용하는 것이 어색하거나 어려울 수 있습니다.


"최신 상태를 유지하지 못한 엔지니어": 엔지니어는 비주류 기술에 대해서도 배워야한다고 말할 수 있습니다. 불변성에 대한 아이디어는 최근에야 주류가되었지만 매우 오래된 아이디어이며 Scheme, SML, Haskell과 같은 이전 언어에서 지원됩니다 (강제되지 않은 경우). 따라서 주류 언어 이외의 언어에 익숙한 사람은 30 년 전에도 배울 수있었습니다.
Giorgio

@Giorgio : 일부 국가에서는 많은 엔지니어 들이 LINQ, FP, 반복자 및 제네릭없이 C # 코드를 작성하므로 실제로 2003 년 이후 C #에서 발생한 모든 사항을 놓쳤습니다. 선호 언어를 모르는 경우 , 나는 그들이 주류가 아닌 언어를 알고 있다고 거의 상상하지 못한다.
Arseni Mourzenko

@MainMa : 이탤릭체로 엔지니어 라는 단어를 썼습니다 .
Giorgio

@Giorgio : 우리 나라에서는 건축가 , 컨설턴트 , 그리고 다른 이질적인 용어 로도 불리며 이탤릭체로 쓰여지지 않았습니다. 현재 일하고있는 회사에서 분석가 개발자 라고하며, 시간이 오래 걸리는 기존 HTML 코드 용 CSS를 작성해야합니다. 직책은 여러 수준에서 혼란스러워합니다.
Arseni Mourzenko

1
@MainMa : 동의합니다. 엔지니어 나 컨설턴트와 같은 직종은 널리 인정되는 의미가없는 용어 일뿐입니다. 그들은 종종 사람이나 자신의 위치를 ​​실제보다 더 중요하고 권위있게 만드는 데 사용됩니다.
Giorgio
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.