Değişmez tiplerin dezavantajları nelerdir?


12

Sınıf örneklerinin değişmesi beklenmediğinde kendimi gittikçe daha fazla değişmez tür kullandığımı görüyorum . Daha fazla çalışma gerektirir (aşağıdaki örneğe bakın), ancak türlerin çok iş parçacıklı bir ortamda kullanılmasını kolaylaştırır.

Aynı zamanda, değişebilirlik kimseye fayda sağlamayacak olsa bile, diğer uygulamalarda değişmez türleri nadiren görüyorum.

Soru: Değişmez tipler neden diğer uygulamalarda bu kadar nadir kullanılır?

  • Bu değişmez bir tip için kod yazmak daha uzun olduğu için mi,
  • Yoksa bir şeyleri özlüyor muyum ve değişmez türleri kullanırken bazı önemli dezavantajlar var mı?

Gerçek hayattan örnek

Diyelim ki böyle Weatherbir RESTful API'den aldınız :

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

Genel olarak gördüğümüz şey (kodu kısaltmak için yeni satırlar ve yorumlar kaldırıldı):

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; }
}

Öte yandan, WeatherAPI'den bir almanın , daha sonra değiştirmenin garip olacağı göz önüne alındığında, bu şekilde yazardım: gerçek dünyadaki havayı değiştirmek Temperatureveya Skydeğiştirmek CorrespondingCityistemez ve değiştirmek de hiçbir anlam ifade etmez.

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
“Daha fazla iş gerektirir” - atıf gerekli. Deneyimlerime göre daha az çalışma gerektirir .
Konrad Rudolph

1
@KonradRudolph: Daha fazla çalışma ile , değişmez bir sınıf yaratmak için yazmak için daha fazla kod demek istiyorum. Sorumdan gelen örnek bunu, değişken bir sınıf için 7 satır ve değişmez bir sınıf için 19 satır ile göstermektedir.
Arseni Mourzenko

Visual Studio'da Kod Parçacıkları özelliğini kullanarak kod yazmayı azaltabilirsiniz. Özel snippet'lerinizi oluşturabilir ve IDE'nin alanı ve özelliği aynı anda birkaç tuşla tanımlamasını sağlayabilirsiniz. Değişmez tipler çoklu iş parçacığı için gereklidir ve Scala gibi dillerde yaygın olarak kullanılır.
Mert Akcakaya

@Mert: Kod parçacıkları basit şeyler için mükemmeldir. Alanların ve özelliklerin yorumları ve doğru sıralamayla tam bir sınıf oluşturacak bir kod snippet'i yazmak kolay bir iş değildir.
Arseni Mourzenko

5
Verilen örneğe katılmıyorum, değişmez versiyon daha farklı şeyler yapıyor. Örnek düzeyindeki değişkenleri erişimcilerle özellikler bildirerek kaldırabilirsiniz ve {get; private set;}değişebilir bile bir kurucuya sahip olmalıdır, çünkü bu alanların tümü her zaman ayarlanmalıdır ve neden bunu zorlamıyorsunuz? Bu iki mükemmel makul değişikliği yapmak onları öne çıkarır ve LoC paritesini getirir.
Phoshi

Yanıtlar:


16

Ben C # ve Objective-C program. Değişmez yazmayı gerçekten seviyorum, ancak gerçek hayatta aşağıdaki nedenlerden dolayı, özellikle veri türleri için kullanımını her zaman sınırlamak zorunda kaldım:

  1. Değişken tiplere göre uygulama çabası . Değişmez tipte, tüm özellikler için argümanlar gerektiren bir kurucuya sahip olmanız gerekir. Örneğin iyi bir örnek. Her biri 5-10 özelliğe sahip 10 sınıfınız olduğunu hayal etmeyi deneyin. İşleri kolaylaştırmak için, değiştirilmiş değişmez örnekleri C # ' a StringBuilderveya buna benzer bir şekilde UriBuilderveya WeatherBuildersizin durumunuzda oluşturmak veya oluşturmak için bir oluşturucu sınıfınızın olması gerekebilir . Tasarladığım birçok sınıfın böyle bir çabaya değmediği için bu benim için ana nedendir.
  2. Tüketici kullanılabilirliği . Değişmez bir tipin değişebilir tipe kıyasla kullanımı daha zordur. Örnekleme için tüm değerlerin başlatılması gerekir. Değişmezlik ayrıca, örneği bir oluşturucu kullanmadan değerini değiştirmek için bir yönteme geçiremeyeceğimiz anlamına gelir ve bir oluşturucuya ihtiyacımız varsa dezavantajı benim (1) 'mdedir.
  3. Dilin çerçevesine uyumluluk. Veri çerçevelerinin çoğunun çalışması için değiştirilebilir türler ve varsayılan kurucular gerekir. Örneğin, değiştirilemez türlerle iç içe LINQ-SQL sorgusu yapamazsınız ve Windows Forms 'TextBox gibi editörlerde düzenlenecek özellikleri bağlayamazsınız.

Kısacası, değişmezlik, değer gibi davranan veya sadece birkaç özelliği olan nesneler için iyidir. Değişmez bir şey yapmadan önce, gerekli çabayı ve değişmez hale getirdikten sonra sınıfın kullanılabilirliğini dikkate almalısınız.


11
"Örnekleme önceden tüm değerlere ihtiyaç duyar.": Ayrıca, başlatılmamış alanların etrafında yüzen bir nesneye sahip olma riskini kabul etmedikçe, değiştirilebilir bir tür de gerekir ...
Giorgio

@Giorgio Değişebilir tip için, varsayılan kurucu örneği varsayılan duruma başlatmalıdır ve örneğin durumu somutlaştırıldıktan sonra daha sonra değiştirilebilir.
tia

8
Değişmez bir tür için aynı varsayılan kurucuya sahip olabilir ve daha sonra başka bir kurucu kullanarak bir kopyasını oluşturabilirsiniz. Varsayılan değerler değiştirilebilir tip için geçerliyse, her iki durumda da aynı objeyi modellediğiniz için değişmez tip için de geçerli olmalıdır. Veya fark nedir?
Giorgio

1
Dikkate alınması gereken bir şey daha, türün neyi temsil ettiği. Veri sözleşmeleri, tüm bu noktalar nedeniyle iyi değiştirilemez türler için geçerli değildir, ancak bağımlılıklar ile başlatılan veya yalnızca verileri okuyan ve ardından işlemleri gerçekleştiren hizmet türleri değişmezlik için mükemmeldir, çünkü işlemler tutarlı bir şekilde çalışır ve hizmet durumu olamaz riski değişti.
Kevin

1
Şu anda değişmezliğin varsayılan olduğu F # 'da kod yazıyorum (bu nedenle uygulanması daha kolay). 3. noktanızın büyük engel olduğunu görüyorum. En standart .Net kütüphanelerini kullandığınız anda, kasnaklar arasında atlamanız gerekir. (Ya yansıma kullanırlar ve böylece değişmezliği atlarlarsa ... argh!)
Guran

5

Sadece genellikle değişmezlik etrafında dönmeyen dillerde oluşturulan değişmez türler, istenen değişiklikleri ifade etmek için bazı "oluşturucu" nesne türüne ihtiyaç duymaları durumunda potansiyel olarak kullanımın yanı sıra, daha fazla geliştirici zamana mal olma eğiliminde olacaktır (bu, genel olarak iş daha fazla olacak, ancak bu durumlarda önceden bir maliyet var). Ayrıca, dilin değişmez türler oluşturmayı gerçekten kolay hale getirip getirmediğine bakılmaksızın, önemsiz olmayan veri türleri için her zaman biraz işlem ve bellek ek yükü gerektirme eğiliminde olacaktır.

Yan Etkilerden Yararlanma İşlevleri Yapma

Değişmezlik etrafında dönmeyen dillerde çalışıyorsanız, pragmatik yaklaşımın her bir veri türünü değişmez kılmak istemediğini düşünüyorum. Aynı avantajların çoğunu sağlayan potansiyel olarak çok daha üretken bir zihniyet, sisteminizde sıfır yan etkiye neden olan işlevlerin sayısını en üst düzeye çıkarmaya odaklanmaktır .

Basit bir örnek olarak, böyle bir yan etkiye neden olan bir fonksiyonunuz varsa:

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

O zaman bu işlevi yan etkilerden kaçınmak için başlatma sonrası atama gibi operatörleri yasaklayan değişmez bir tamsayı veri türüne ihtiyacımız yoktur. Bunu basitçe yapabiliriz:

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

Şimdi fonksiyon karmaşa xdışında veya kapsamı dışında bir şey değil ve bu önemsiz durumda dolaylı / örtüşme ile ilişkili herhangi bir ek yükten kaçınarak bazı döngüleri traş etmiş olabiliriz. En azından ikinci versiyon, birincisinden daha pahalı bir şekilde pahalı olmamalıdır.

Tam Olarak Kopyalamak Pahalı Olan Şeyler

Tabii ki, yan etkilere neden olan bir işlev yapmaktan kaçınmak istiyorsak, çoğu vaka bu kadar önemsiz değildir. Karmaşık bir gerçek dünya kullanım durumu daha çok şöyle olabilir:

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

Bu noktada ağ, yüz binden fazla çokgen, daha fazla köşe ve kenar, çoklu doku haritaları, morph hedefleri vb. İle birkaç yüz megabayt bellek gerektirebilir. Bunu yapmak için tüm ağın kopyalanması gerçekten pahalı olurdu. transformaşağıdaki gibi yan etkilerden arınmış:

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

Ve bu durumda, bir şeyin tamamını kopyalamanın normalde destansı bir yük olacağı yerlerde Mesh, değiştirilmiş versiyonları oluşturmak için kalıcı bir veri yapısına ve analog "oluşturucu" ile değişmez bir türe dönüşmenin yararlı olduğunu gördüm . basit olmayan sığ kopya ve referans olmayan benzersiz parçalar saymak olabilir. Tüm bunlar, yan etkilerden arındırılmış mesh işlevlerini yazabilme odağıdır.

Kalıcı Veri Yapıları

Ve her şeyin kopyalanmasının inanılmaz derecede pahalı olduğu bu durumlarda, Meshaçık bir şekilde biraz dik bir maliyeti olmasına rağmen gerçekten ödeyecek bir değişmez tasarlama çabasını buldum , çünkü sadece iplik güvenliğini basitleştirmedi. Ayrıca, tahribatsız düzenlemeyi basitleştirdi (kullanıcının orijinal kopyasını değiştirmeden mesh işlemlerini katmanlamasına izin verdi), geri al sistemleri (şimdi geri alma sistemi, bir işlem tarafından yapılan bellek değişikliklerinden önce yapılan değişikliklerden önce meshin değişmez bir kopyasını saklayabilir kullanım) ve istisna güvenliği (şimdi yukarıdaki işlevde bir istisna oluşursa, işlevin tüm yan etkilerini geri alması ve geri alması gerekmez, çünkü herhangi biriyle başlamasına neden olmaz).

Bu durumlarda, bu iri veri yapılarını değişmez hale getirmek için gereken zamanın maliyetinden daha fazla zaman kazandığını söyleyebilirim, çünkü bu yeni tasarımların bakım maliyetlerini, değişebilirlik ve yan etkilere neden olan işlevler etrafında dönen eski tasarımlarla karşılaştırdım, ve eski değişebilir tasarımlar çok daha fazla zamana mal oldu ve özellikle geliştiricilerin istisna güvenliği gibi krizi zamanında ihmal etmeleri için gerçekten cazip olan alanlarda insan hatasına çok daha eğilimliydi.

Bu yüzden değişmez veri türlerinin bu durumlarda gerçekten işe yaradığını düşünüyorum, ancak sisteminizdeki işlevlerin çoğunu yan etkilerden arındırmak için her şeyin değişmez olması gerekmez. Birçok şey tam kopyalamak için yeterince ucuz. Ayrıca birçok gerçek dünya uygulamasının burada ve orada bazı yan etkilere neden olması gerekecektir (en azından bir dosyayı kaydetmek gibi), ancak tipik olarak yan etkilerden yoksun olabilecek çok daha fazla işlev vardır.

Bana bazı değişmez veri türlerine sahip olmanın amacı, sadece küçük bölümlerde tam olarak sola ve sağa büyük kopyalama yapılarak epik ek yüke maruz kalmadan, yan etkilerden arınmış maksimum işlev sayısını yazabildiğimizden emin olmaktır. bunların değiştirilmesi gerekir. Bu durumlarda kalıcı veri yapılarına sahip olmak, işlevlerimizi epik bir maliyet ödemeden yan etkilerden arınmış olarak yazmamızı sağlayan bir optimizasyon detayı haline gelir.

Değişmez Tepegöz

Şimdi kavramsal olarak değiştirilebilir versiyonlar her zaman verimlilikte bir avantaj sağlayacaktır. Değişmez veri yapıları ile ilişkili her zaman hesaplama yükü vardır. Ama yukarıda tarif ettiğim durumlarda değerli bir değişim buldum ve yükü doğada yeterince asgariye indirmeye odaklanabilirsiniz. Doğruluğun kolaylaştığı ve optimizasyonun kolaylaşmasından ziyade optimizasyonun zorlaştığı ancak doğruluğun zorlaştığı bu yaklaşımı tercih ederim. Yanlış sonuçlara ne kadar çabuk ulaşırsa başlasın, doğru şekilde çalışmayan kod üzerinde biraz daha fazla ayarlamaya ihtiyaç duyan mükemmel bir şekilde doğru şekilde çalışan bir koda sahip olmak neredeyse demoralize değil.


3

Düşünebileceğim tek dezavantaj, teoride değişmez verilerin kullanımının değişebilir verilerden daha yavaş olabileceğidir - yeni bir örnek oluşturmak ve bir öncekini mevcut olanı değiştirmekten daha yavaş toplamaktır.

Diğer "sorun" ise sadece değişmez türleri kullanamayacağınızdır. Sonunda durumu tarif etmelisiniz ve bunu yapmak için değişebilir tipler kullanmalısınız - durumu değiştirmeden herhangi bir iş yapamazsınız.

Ama yine de genel kural, mümkün olan her yerde değişmez türleri kullanmak ve sadece gerçekten bir neden olduğunda türleri değişebilir yapmaktır ...

Ve " Diğer uygulamalarda neden bu kadar az kullanılan türler nadiren kullanılır? " http://www.javapractices.com/topic/TopicAction.do?Id=29


1
Her iki sorunun da Haskell'de değil.
Florian Margaine

@FlorianMargaine Ayrıntı verebilir misiniz?
mrpyo

Akıllı derleyici sayesinde yavaşlık doğru değildir. Ve Haskell'de, I / O bile değişmez bir API yoluyla.
Florian Margaine

2
Hızdan daha temel bir sorun, değişmez cisimlerin durumları değişirken kimliğini korumanın zor olmasıdır. Değişken bir Carnesne belirli bir fiziksel otomobilin konumu ile sürekli olarak güncellenirse, o nesneye bir referansım varsa, o otomobilin nerede olduğunu hızlı ve kolay bir şekilde öğrenebilirim. Eğer Cardeğişmez olduğu, şimdiki nerede bulma olasılığı çok zor olurdu.
SuperCat

Bazen derleyicinin geride bırakılan önceki nesneye referans olmadığını ve böylece yerinde değiştirebileceğini veya ormansızlaşma dönüşümlerini yapabileceğini anlaması için oldukça akıllıca kodlamanız gerekir. Özellikle daha büyük programlarda. @Supercat'in dediği gibi, kimlik gerçekten bir sorun haline gelebilir.
Macke

0

İşlerin değişebileceği herhangi bir gerçek dünya sistemini modellemek için, değişebilir durumun bir şekilde, bir şekilde kodlanması gerekecektir. Bir nesnenin değiştirilebilir durumunu tutabilmesinin üç ana yolu vardır:

  • Değişmez bir nesneye değişebilir bir referans kullanma
  • Değişken bir nesneye değişmez referans kullanma
  • Değiştirilebilir bir nesneye değiştirilebilir bir başvuru kullanma

Önce kullanmak, bir nesnenin mevcut durumun değişmez bir anlık görüntüsünü yapmasını kolaylaştırır. İkincisini kullanmak, bir nesnenin mevcut durumun canlı bir görünümünü oluşturmasını kolaylaştırır. Üçüncüyü kullanmak, değişmez anlık görüntülere veya canlı görüntülemelere çok az ihtiyaç duyulduğu durumlarda belirli eylemleri daha verimli hale getirebilir.

Değişken bir nesneye değişebilir bir referans kullanılarak depolanan durumun güncellenmesinin genellikle değişebilir bir nesne kullanılarak depolanan durumun güncellenmesinden daha yavaş olması, değişebilir bir referans kullanılması için kişinin durumun ucuz canlı bir görünümünü oluşturma olasılığını ortadan kaldırması gerekecektir. Bir kişinin canlı görüntü oluşturması gerekmiyorsa, bu bir sorun değildir; bununla birlikte, canlı bir görüntü oluşturmaya ihtiyaç duyulursa, değişmez bir referans kullanamama, tüm işlemleri görünümle yapar - hem okur hem yazar- aksi halde olduğundan çok daha yavaş. Değiştirilemeyen anlık görüntülere ihtiyaç duyulması, canlı görüntüleme ihtiyacını aşarsa, değiştirilemeyen anlık görüntülerin iyileştirilmiş performansı, canlı görüntüler için performans isabetini haklı gösterebilir, ancak canlı görüntülemeye ihtiyaç duyuyor ve anlık görüntülere ihtiyaç duymuyorsa, değişebilir nesnelere değişmez referanslar kullanmak gitmek.


0

Sizin durumunuzda cevap esas olarak C # 'nın Değişmezlik için zayıf desteğe sahip olmasıdır ...

Aşağıdaki durumlarda harika olur:

  • aksi belirtilmedikçe her şey varsayılan olarak değiştirilemez (yani 'değişebilir' bir anahtar kelimeyle), değişmez ve değişebilir türlerin karıştırılması kafa karıştırıcıdır

  • mutasyon yöntemleri ( With) otomatik olarak kullanılabilir olacak - bu zaten elde edilebilir rağmen bkz .

  • belirli bir yöntem çağrısının sonucunun (yani ImmutableList<T>.Add) atılamayacağını veya en azından bir uyarı üreteceğini söylemenin bir yolu olacaktır.

  • Ve esas olarak derleyici istenildiği kadar değişmezliği sağlayabiliyorsa (bkz. Https://github.com/dotnet/roslyn/issues/159 )


1
Üçüncü nokta olarak, ReSharper MustUseReturnValueAttributetam olarak bunu yapan bir özel niteliğe sahiptir. PureAttributeaynı etkiye sahiptir ve bunun için daha da iyidir.
Sebastian Redl

-1

Değişmez tipler neden diğer uygulamalarda bu kadar nadir kullanılır?

Cehalet? Tecrübesizlik?

Değişmez nesneler bugün yaygın olarak kabul edilmektedir, ancak nispeten yeni bir gelişmedir. Güncel olmayan veya sadece 'bildiklerine' yapışmış mühendisler onları kullanmayacaktır. Ve bunları etkili bir şekilde kullanmak için biraz tasarım değişikliği gerekiyor. Uygulamalar eskiyse veya tasarım becerilerinde mühendisler zayıfsa, bunları kullanmak garip veya başka bir şekilde zahmetli olabilir.


"Güncel olmayan mühendisler": Bir mühendisin ana akım olmayan teknolojiler hakkında da bilgi edinmesi gerektiğini söyleyebiliriz. Değişmezlik fikri sadece son zamanlarda yaygınlaşıyor, ancak oldukça eski bir fikir ve Scheme, SML, Haskell gibi eski diller tarafından destekleniyor (desteklenmiyorsa). Dolayısıyla, ana akım dillerin ötesine bakmaya alışkın olan herkes 30 yıl önce bile bunu öğrenmiş olabilir.
Giorgio

@Giorgio: bazı ülkelerde, birçok mühendis hala LINQ olmadan, FP olmadan, yineleyiciler olmadan ve jenerikler olmadan C # kodunu yazıyor, bu yüzden aslında 2003'ten beri C # ile olan her şeyi bir şekilde kaçırdılar. Tercih dillerini bile bilmiyorlarsa , Ana akım olmayan bir dil bildiklerini hayal etmiyorum.
Arseni Mourzenko

@MainMa: Mühendisleri italik olarak yazdığınız için iyi .
Giorgio

@Giorgio: Ülkemde, aynı zamanda mimarlar , danışmanlar ve daha önce italik yazılmayan birçok vainglorious terim de denir . Şu anda çalıştığım şirkette analist geliştiricisi olarak adlandırıldım ve zamanımı berbat eski HTML kodu için CSS yazarak harcamam bekleniyor. İş unvanları pek çok düzeyde rahatsız edici.
Arseni Mourzenko

1
@MainMa: Kabul ediyorum. Mühendis veya danışman gibi başlıklar genellikle yaygın olarak kabul edilen bir anlamı olmayan terimlerdir. Genellikle birini veya pozisyonlarını olduğundan daha önemli / prestijli hale getirmek için kullanılırlar.
Giorgio
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.