Grokking Java kültürü - neden bu kadar ağır? Ne için optimize ediyor? [kapalı]


288

Python'da kod yazıyordum. Şimdi, iş nedeniyle, Java kodluyorum. Yaptığım projeler oldukça küçük ve muhtemelen Python daha iyi çalışacaktı ancak Java kullanmanın mühendislik dışı geçerli nedenleri var (ayrıntılara giremiyorum).

Java sözdizimi sorun değil; bu sadece başka bir dil. Ancak sözdiziminin yanı sıra, Java'nın bir kültürü, bir dizi geliştirme yöntemi ve "doğru" olduğu düşünülen uygulamaları vardır. Şimdilik bu kültürü "çiğnemek" konusunda tamamen başarısız oluyorum. Bu yüzden doğru yönde açıklamaları veya işaretçileri gerçekten takdir ediyorum.

Başlattığım bir Yığın Taşması sorusunda asgari eksiksiz bir örnek mevcuttur: https://stackoverflow.com/questions/43619566/returning-a-result-with-several-values-the-java-way/43620339

Bir görevim var - ayrıştırmak (tek bir dizgeden) ve üç değerlik bir kümeyi ele almak. Python'da bir astardır (demet), Pascal veya C'de 5 astarlı bir kayıt / yapıdır.

Cevaplara göre, bir yapının eşdeğeri Java sözdiziminde ve üçlü yaygın olarak kullanılan bir Apache kütüphanesinde mevcuttur - ancak bunu yapmanın "doğru" yolu aslında değer için ayrı bir sınıf oluşturarak, alıcılar ve ayarlayıcılar. Birisi tam bir örnek vermek için çok nazikti. 47 kod satırı vardı (bu satırların bazıları boşlukluydu).

Büyük bir kalkınma topluluğunun muhtemelen "yanlış" olmadığını anlıyorum. Yani bu benim anlayışımda bir sorun.

Python uygulamaları okunabilirlik için optimize eder (bu felsefede, sürdürülebilirliği sağlar) ve bundan sonra gelişme hızı. C uygulamaları kaynak kullanımı için optimize eder. Java uygulamaları neleri optimize ediyor? En iyi tahminim ölçeklenebilirlik (her şey milyonlarca LOC projesi için hazır durumda olmalıdır), ancak bu çok zayıf bir tahmin.


1
Sorunun teknik yönlerini cevaplamayacak ama yine de bağlamda: softwareengineering.stackexchange.com/a/63145/13899
Newtopian

74


3
İşte bence doğru cevap budur. Java'yı büyütmüyorsunuz, çünkü bir sysadmin gibi programlamayı düşünüyorsunuz, programcı değil. Size göre, yazılım en uygun şekilde belirli bir görevi başarmak için kullandığınız bir şeydir. Java kodunu 20 yıldır yazıyorum ve üzerinde çalıştığım bazı projelerin elde edilmesi için 20, 2 yıllık bir ekip var. Java, Python'un yerine geçmez. İkisi de işlerini yapıyor, ancak işleri tamamen farklı.
Richard

1
Java'nın fiili standart olmasının nedeni, basitçe doğru olmasıdır. İlk kez, sakalları modaya çıkmadan önce sakalları olan ciddi insanlar tarafından yaratıldı. Dosyaları değiştirmek için kabuk komut dosyalarını çökertmeye alışkınsanız, Java katlanılmaz şekilde şişirilmiş görünüyor. Ancak, kümelenmiş redis önbelleklemesi, 3 faturalandırma sistemi ve 20 milyon kiloluk bir oracle veritabanı kümesi tarafından desteklenen, günde 50 milyon kişiye hizmet verebilecek çift yedekli bir sunucu kümesi oluşturmak istiyorsanız. Python'daki veritabanı 25 satır daha az koddur.
Richard

Yanıtlar:


237

Java dili

Tüm bu cevapların Java'nın çalışma biçimine niyet atfetmeye çalışarak eksik olduğunu düşünüyorum. Java'nın ayrıntılılığı, Python ve diğer birçok dilde henüz terser sözdizimine sahip olduğu için nesne yönelimli olmaktan kaynaklanmıyor. Java'nın ayrıntılı yanı, erişim değiştiricilerin de desteğinden gelmiyor. Bunun yerine, basitçe Java nasıl tasarlandı ve gelişti.

Java başlangıçta OO ile biraz geliştirilmiş bir C olarak yaratıldı. Gibi Java 70'lerin dönemi sözdizimi vardır. Ayrıca, Java geriye dönük uyumluluğu korumak ve zamana dayanmasını sağlamak için özellik ekleme konusunda oldukça tutucu. Java, 2005'te, XML'in dilin öfkeli olduğu ve hiç kimsenin umursamadığı ve 10 yıl sonra evrimini sınırlayan hayalet özellikleriyle şişirildiği öfke gibi modaya uygun özellikler eklemiş olsaydı. Dolayısıyla Java, kavramları tersten ifade etmek için birçok modern sözdiziminden yoksundur.

Ancak, Java'nın bu sözdizimini benimsemesini engelleyen temel bir şey yoktur. Örneğin, Java 8, lambda ve yöntem referansları ekleyerek birçok durumda ayrıntı ayrıntılarını büyük ölçüde azaltır. Java da benzer şekilde Scala'nın vaka sınıfları gibi kompakt veri tipi bildirimleri için destek sağlayabilir. Ancak Java bunu yapmadı. Özel değer türlerinin ufukta olduğunu ve bu özelliğin bunları bildirmek için yeni bir sözdizimi getirebileceğini unutmayın. Sanırım göreceğiz.


Java Kültürü

Girişimci Java geliştirme tarihi büyük ölçüde bizi bugün gördüğümüz kültüre yönlendirdi. 90'ların sonunda / 00'ların başında, Java, sunucu tarafı iş uygulamaları için son derece popüler bir dil haline geldi. O zamanlar bu uygulamalar büyük ölçüde geçici olarak yazıldı ve HTTP API'leri, veritabanları ve XML yayınlarını işleme gibi birçok karmaşık endişeyi içeriyordu.

00'larda, bu uygulamaların birçoğunun ortak olduğu ve Hibernate ORM, Xerces XML ayrıştırıcısı, JSP'ler ve sunucu uygulaması API'si ve EJB gibi bu kaygıları yönetmek için çerçeveleri olduğu açıkça ortaya çıktı. Bununla birlikte, bu çerçeveler, otomatikleştirmek için belirledikleri belirli bir alanda çalışma çabalarını azaltırken, yapılandırma ve koordinasyona ihtiyaç duyuyorlardı. O zaman, ne sebeple olursa olsun, en karmaşık kullanım durumuna hitap etmek için çerçeveler yazmak popülerdi ve bu nedenle bu kütüphaneler kurmak ve bütünleşmek için karmaşıktı. Ve zamanla, özellikleri biriktirdiklerinde giderek daha karmaşık hale geldiler. Java kurumsal gelişimi giderek üçüncü taraf kütüphanelerini bir araya getirme ve yazma algoritmaları hakkında daha az bilgi edinmeye başladı.

Sonunda, kurumsal araçların sıkıcı konfigürasyonu ve yönetimi, ağların, özellikle de Bahar çerçevesinin, yönetimi yönetmek için ortaya çıktığı kadar acı vericiydi. Tüm konfigürasyonunuzu tek bir yere koyabilirsiniz, teori gitti ve konfigürasyon aracı daha sonra parçaları konfigüre edip birleştirirdi. Ne yazık ki bu "çerçeve çerçeveleri" bütün balmumu topunun üstüne daha fazla soyutlama ve karmaşıklık kattı .

Geçtiğimiz birkaç yıl boyunca, daha hafif kütüphaneler popülerlik kazanmıştır. Bununla birlikte, yoğun bir kurumsal çerçevelerin büyümesi sırasında, bütün bir Java jeneratörü kuşağı gelmişti. Onların çerçevesini geliştiren rol modelleri, fabrika fabrikaları ve vekil konfigürasyon fasulye yükleyicileri yazdı. Bu canavarlıkları günden güne yapılandırmak ve entegre etmek zorunda kaldılar. Sonuç olarak, bir bütün olarak topluluğun kültürü, bu çerçevelerin örneğini takip etti ve fazlasıyla mühendisliğe yöneldi.


15
Komik olan, diğer dillerin biraz "java-isms" alıyor gibi görünmesi. PHP OO özellikleri Java'dan büyük ölçüde esinlenmiştir ve günümüzde JavaScript, büyük miktardaki çerçeveleri ve tüm bağımlılıkları toplamanın ve yeni bir uygulama başlatmanın ne kadar zor olduğu nedeniyle eleştirilmektedir.
marcus

14
@ marcus belki bazı insanlar nihayet "tekerleği yeniden icat etmeyin" şeyini öğrendi diye mi? Bağımlılıklar,
nihayetinde

9
"Terse" çoğu kez arcane, "zeki" , code-golf-ish one- liner'a çevirir .
Tulains Córdova

7
Düşünceli, iyi yazılmış cevap. Tarihsel bağlam Göreceli olarak açıklanmamış. Güzel bitti.
Cheezmeister

10
@ TulainsCórdova "Veba gibi zekice hilelerden kaçınmalıyız" (Donald Knuth) olması gerektiğine katılıyorum, ancak bu daha uygun (ve okunabilir) forolduğunda bir döngü yazmak anlamına gelmez map. Mutlu bir ortam var.
Brian McCutchon

73

Diğerlerinin ortaya çıkarmamış olduğu noktalardan birine cevap verdiğime inanıyorum.

Java, derleme zamanı programcısı hatalarının tespiti için hiçbir varsayımda bulunmadan optimize eder.

Genel olarak, Java, yalnızca programcının niyetini açıkça ifade etmesinden sonra, kaynak koduyla ilgili gerçekleri çıkarmaya meyillidir. Java derleyicisi hiçbir zaman kodla ilgili herhangi bir varsayımda bulunmaz ve yalnızca fazlalık kodu azaltmak için çıkarımı kullanır.

Bu felsefenin ardındaki sebep, programcının sadece insan olmasıdır. Yazdıklarımız her zaman programın gerçekten yapmayı düşündüğü şey değildir. Java dili, geliştiriciyi türlerini her zaman açıkça bildirmeye zorlayarak bu sorunlardan bazılarını azaltmaya çalışır. Bu sadece, yazılan kodun aslında amaçlananı yapıp yapmadığını kontrol etmenin bir yolu.

Diğer bazı diller, ön koşulları, sonrası koşulları ve değişmezleri kontrol ederek bu mantığı daha da ileriye götürür (derleme zamanında yaptıklarından emin değilim). Bunlar, programcının derleyicinin kendi çalışmalarını iki kez kontrol etmesini sağlaması için daha aşırı yollardır.

Sizin durumunuzda, bu derleyicinin, iade edeceğini düşündüğünüz türleri iade edeceğinizi garanti etmesi için bu bilgiyi derleyiciye vermeniz gerektiği anlamına gelir.

Java'da bunu yapmanın iki yolu vardır:

  1. Kullanım Triplet<A, B, C>gerçekten olması gereken (dönüş türü olarak java.util, hem değil Gerçekten neden açıklayamam. JDK8 tanıtmak Özelliklede Function, BiFunction, Consumer, BiConsumer, vb ... Bu sadece görünüyor Pairve Tripleten azından mantıklı. Ama konuyu dağıtmak )

  2. Her alanın uygun şekilde adlandırıldığı ve yazıldığı bu amaç için kendi değer türünüzü oluşturun.

Her iki durumda da, derleyici, işlevinizin bildirdiği türleri döndürdüğünü ve arayanın, geri gönderilen her alanın türünü fark ettiğini ve bunları uygun şekilde kullandığını garanti edebilir.

Bazı diller aynı anda statik tip kontrolü AND tipi çıkarımı sağlar, ancak bu, kapının ince bir sınıf uyumsuzluğu sınıfı için açık kalmasına neden olur. Geliştiricinin belirli bir türden bir değer döndürmeyi amaçladığı, ancak gerçekte bir başkasını döndürdüğü ve derleyicinin STILL kodu kabul ettiği için, çünkü hem işlev hem de arayan kişi yalnızca amaçlanan her ikisine de uygulanabilecek yöntemleri kullanır. ve gerçek tipler.

Açık yazı yazmak yerine yazı çıkarımının kullanıldığı yazı tipinde (veya akış türünde) buna benzer bir şey düşünün.

function parseDurationInMillis(csvLine){
    // here the developer intends to return a Number, 
    // but actually it's a String
    return csv.firstField();
}

// Compiler infers that parseDurationInMillis is String, so it does
// string concatenation and infers that plusTwoSeconds is String
// Developer actually intended Number
var plusTwoSeconds = 2000 + parseDurationInMillis(csvLine);

Bu çok saçma bir durum, elbette, ama kod daha doğru görünüyor , çünkü çok daha ince ve hata ayıklamada zor olabilir . Java'da bu tür sorunlardan tamamen kaçınılmaktadır ve tüm dilin etrafında tasarlandığı şey budur.


Uygun Nesne Yönelimli prensipler ve etki alanına dayalı modellemenin ardından, bağlantılı sorudaki süre ayrıştırma durumunun java.time.Duration, yukarıdaki durumların her ikisinden de daha açık olan bir nesne olarak da iade edilebileceğini unutmayın .


38
Java'nın kod doğruluğunu en iyi duruma getirdiğini belirtmek geçerli bir nokta olabilir, ancak dilin ya başarısız olduğu ya da son derece yetersiz olduğu anlaşılıyor (bana bir sürü dil programlama, son zamanlarda biraz java). Kabul edildi, Java eski ve pek çok şey yoktu, ama Haskell (ve söylemeye cüret edersem? Rust veya Go) gibi daha iyi doğruluk kontrolü sağlayan modern alternatifler var. Java’nın bütün tatsızlığı bu amaç için gereksizdir. - Ve ayrıca, bu olmaz hiç kültürünü açıklamak ancak Solomonoff kültür BS zaten olduğunu ortaya çıkardı.
17'de,

8
Bu örnek, yazımsal çıkarımın, yalnızca JavaScript’in dinamik bir dilde örtük dönüşümlere izin verme kararının aptalca olduğu sorun olduğunu göstermez. Mantıklı herhangi bir dinamik dil veya tür çıkarımı olan statik dilli bir dil buna izin vermez. Her iki tür için de örnek olarak: python bir çalışma zamanı hariç tutulacaktı, Haskell derlemedi.
Voo

39
@tomsmeding Bu tartışmanın özellikle aptalca olduğuna dikkat etmek önemlidir, çünkü Haskell Java'dan 5 yıldan daha uzun süredir. Java bir tür sisteme sahip olabilir, ancak piyasaya sürüldüğünde son teknoloji doğruluk doğrulaması yapılmadı.
Alexis King,

21
“Java, derleme zamanı hata algılaması için en iyi duruma getirme” - Hayır. Bunu sağlar , ancak diğerlerinin de belirttiği gibi, kesinlikle, kelimenin tam anlamıyla onun için en iyi duruma gelmez. Dahası, bu argüman olduğunu tamamen alakasız diğer diller çünkü OP'ın sorusuna do aslında derleme zamanı hatası tespiti için optimize benzer senaryolar için çok daha hafif bir sözdizimi vardır. Java'nın en üst seviyedeki ayrıntısızlığının derleme zamanı doğrulamasıyla hiçbir ilgisi yoktur.
Konrad Rudolph

19
@KonradRudolph Evet, bu cevap tamamen saçma, ancak duygusal olarak çekici olduğunu düşünüyorum. Neden bu kadar çok oy aldığından emin değilim. Haskell (veya belki de daha da önemlisi, Idris) doğruluk için Java'dan çok daha fazla optimize eder ve hafif bir sözdizimine sahip tüperleri vardır. Dahası, bir veri türünü tanımlamak bir liner'dir ve Java sürümünün alacağı her şeyi alırsınız. Bu cevap, zayıf dil tasarımı ve gereksiz ayrıntı için bir bahanedir, ancak Java'yı seviyorsanız ve diğer dilleri bilmiyorsanız kulağa hoş geliyor .
Alexis King,

50

Java ve Python en çok kullandığım iki dil ama ben diğer yönden geliyorum. Yani, Python'u kullanmaya başlamadan önce Java dünyasında derindeydim, bu yüzden yardımcı olabilirdim. "Neden bu kadar ağır şeyler" sorusunun cevabının 2 şeye düştüğünü düşünüyorum:

  • İkisi arasındaki gelişme ile ilgili maliyetler, uzun balonlu bir hayvan balonundaki hava gibidir. Balonun bir kısmını sıkıp diğer kısmı şişebilir. Python erken kısmı sıkma eğilimindedir. Java sonraki kısmı sıkar.
  • Java hala bu ağırlığın bir kısmını kaldıracak özelliklerden yoksundur. Java 8 bu konuda büyük bir engel oluşturdu ancak kültür değişiklikleri tam olarak sindirmedi. Java gibi birkaç şey daha kullanabilirsiniz yield.

Java, uzun yıllar boyunca büyük insan grupları tarafından sürdürülecek yüksek değerli yazılımlar için 'optimizasyon' yapar. Python'da bir şeyler yazma ve bir yıl sonra bakma deneyimim oldu ve kendi kodumla şaşırdım. Java'da diğer kişilerin kodlarının küçük parçalarına bakabilir ve anında ne yaptığını bilirim. Python'da bunu gerçekten yapamazsınız. Farkında göründüğünden daha iyi biri değil, onların sadece farklı maliyetleri var.

Bahsettiğiniz özel durumda, hiçbir gözü yoktur. Kolay çözüm, ortak değerleri olan bir sınıf oluşturmaktır. Java ilk çıktığında, insanlar bunu düzenli olarak yaptılar. Bunu yapmanın ilk problemi bunun bir baş ağrısı olmasıdır. Bazı mantık veya iş güvenliği eklemek veya polimorfizm kullanmak istiyorsanız, en azından bu 'tuple-esque' nesnesiyle etkileşime giren her sınıfa dokunmanız gerekir. Python'da bunun gibi çözümler var. Bu __getattr__yüzden çok korkunç değil.

Bu konuda bazı kötü alışkanlıklar (IMO) var. Bu durumda, bir demet istiyorsanız, onu neden değişken bir nesne haline getirdiğinizi sorguluyorum. Sadece alıcılara ihtiyaç duymalısınız (bir notta, alma / ayarlama konvansiyonundan nefret ediyorum ama budur) . Diğer bir deyişle, projedeki referansları sınıfa sınırlayarak, sınıfın genel arayüzünü değiştirmeden gerektiğinde daha sonra yeniden değerlendirebilirsiniz. İşte değişmez basit bir nesneyi nasıl yaratabileceğinizi gösteren bir örnek:

public class Blah 
{
  public static Blah blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    return new Blah(number, isInSeconds, lessThanOneMillis);
  }

  private final long number;
  private final boolean isInSeconds;
  private final boolean lessThanOneMillis;

  public Blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    this.number = number;
    this.isInSeconds = isInSeconds;
    this.lessThanOneMillis = lessThanOneMillis;
  }

  public long getNumber()
  {
    return number;
  }

  public boolean isInSeconds()
  {
    return isInSeconds;
  }

  public boolean isLessThanOneMillis()
  {
    return lessThanOneMillis;
  }
}

Bu benim kullandığım türden bir kalıp. Bir IDE kullanmıyorsanız başlamalısınız. Sizin için alıcıları (ve eğer ihtiyacınız varsa belirleyicileri) üretecektir, bu yüzden bu çok acı verici olmaz.

İhtiyaçlarınızın çoğunu burada karşılayacak gibi görünen bir tür olduğunu belirtmeseydim, kendimi çok üzeceğim . Bunu bir kenara koymak, kullandığınız yaklaşımın zayıf yönleriyle oynadığı için Java için uygun değildir, güçlü değil. İşte basit bir gelişme:

public class Blah 
{
  public static Blah fromSeconds(long number)
  {
    return new Blah(number * 1000_000);
  }

  public static Blah fromMills(long number)
  {
    return new Blah(number * 1000);
  }

  public static Blah fromNanos(long number)
  {
    return new Blah(number);
  }

  private final long nanos;

  private Blah(long nanos)
  {
    this.nanos = nanos;
  }

  public long getNanos()
  {
    return nanos;
  }

  public long getMillis()
  {
    return getNanos() / 1000; // or round, whatever your logic is
  }

  public long getSeconds()
  {
    return getMillis() / 1000; // or round, whatever your logic is
  }

  /* I don't really know what this is about but I hope you get the idea */
  public boolean isLessThanOneMillis()
  {
    return getMillis() < 1;
  }
}

Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
maple_shaft

2
Scala'ya "daha modern" Java özellikleriyle ilgili olarak da bakmanızı öneriyorum ...
MikeW

1
@MikeW Aslında ilk sürümlerinde Scala ile başladım ve bir süredir scala forumlarında aktif oldum. Dil tasarımında büyük bir başarı olduğunu düşünüyorum ama aradığım gerçekte aradığım şey değildi ve o toplulukta bir kare mandal olduğum sonucuna vardım. Tekrar bakmalıyım, çünkü o zamandan beri muhtemelen önemli ölçüde değişti.
JimmyJames

Bu cevap OP tarafından gündeme getirilen yaklaşık değer yönünü ele almamaktadır.
ErikE,

@ErikE "Yaklaşık değer" kelimeleri soru metninde görünmez. Beni, ele almadığım sorunun özel kısmına yöneltebilirseniz, bunu yapmaya çalışabilirim.
JimmyJames

41

Java’ya çok kızmadan önce, lütfen cevabınızı diğer yazınızdaki okuyun .

Şikayetinizden biri, bazı değerleri bir cevap olarak döndürmek için bir sınıf oluşturma ihtiyacıdır. Bu, programlama sezgilerinizin doğru yolda olduğunu gösteren bence geçerli bir endişe! Ancak, yaptığınız ilkel saplantı karşıtlığı düzenine sadık kalarak diğer cevapların işareti eksik olduğunu düşünüyorum. Java, Python'da olduğu gibi, birden çok değeri yerel olarak döndürüp kolayca birden çok değişkene atayabileceğiniz birden fazla ilkel ile çalışma kolaylığına sahip değildir.

Ancak bir ApproximateDurationtürün sizin için ne yaptığını düşünmeye başladığınızda , “üç değeri döndürmek için sadece görünüşte gereksiz bir sınıfa” kadar dar bir kapsamın olmadığını fark edersiniz. Bu sınıf tarafından temsil edilen konsept aslında, asıl etki alanı işletme kavramlarınızdan biridir; zamanları yaklaşık olarak gösterebilmeli ve karşılaştırabilmelisiniz. Bunun, uygulamanızın çekirdeğinin her zamanki dilinin bir parçası olması, bunun için iyi bir nesne ve etki alanı desteği olması gerekir, böylece test edilebilir, modüler, yeniden kullanılabilir ve kullanışlı olabilir.

Yaklaşık süreleri bir araya toplayan kodunuz (veya bir hata payına sahip süreleri, ancak bunu temsil ediyorsunuz) tamamen yordam mı yoksa herhangi bir nesne var mı? Yaklaşık sürelerin bir araya getirilmesi etrafındaki iyi tasarımın, kendini test edilebilecek bir sınıf içinde, herhangi bir tüketici kodunun dışında yapmasını zorunlu kılacağını öneririm. Bu tür bir etki alanı nesnesini kullanmanın, kodunuzda, tek bir üst düzey görevi (birçok sorumluluk olsa da) tek bir sorumluluk sınıfına yönlendirmek için sizi tek tek işlem adımlarından uzağa yönlendirmenize yardımcı olacak olumlu bir dalgalanma etkisi olacağını düşünüyorum. farklı kaygıların çatışmalarından arınmış olan.

Örneğin, doğru şekilde çalışmak ve karşılaştırmak için geçen süre için gerçekte hangi hassasiyet veya ölçeğin gerekli olduğu hakkında daha fazla şey öğrendiğinizi ve "yaklaşık 32 milisaniye hata" (kareye yakın) belirtmek için bir ara bayrağa ihtiyacınız olduğunu öğrendiğinizi varsayalım. 1000'in kökeni, yani yarı yarıya logaritmik olarak 1 ile 1000 arasında). Kendinizi, bunu temsil etmek için ilkelleri kullanan bir koda bağladıysanız, koddaki her yeri bulmanız ve kodunu is_in_seconds,is_under_1msdeğiştirmeniz gerekir.is_in_seconds,is_about_32_ms,is_under_1ms. Her şey her yerde değişmek zorunda kalacaktı! Sorumluluğu, hata payını başka bir yerde tüketilebilecek şekilde kaydetmek olan bir sınıf oluşturmak, tüketicilerinizi, hangi hata payının ne olduğu veya bunların nasıl birleştiğine ilişkin herhangi bir şey hakkındaki ayrıntılarını bilmekten kurtarır ve sadece ilgili hata payını belirlemelerine izin verir. Şu an. (Yani, hata marjı doğru olan hiçbir tüketici kodu, sınıfa yeni bir hata marjı eklediğinizde değişmek zorunda değildir, çünkü tüm eski hata marjları hala geçerlidir).

Kapanış Bildirimi

Java'nın ağırlığından şikayet, SOLID ve GRASP ve daha gelişmiş yazılım mühendisliği prensiplerine yaklaştıkça kayboluyor gibi görünüyor.

ek

C # 'nın otomatik özelliklerinin ve yapıcılara yalnızca alma özelliklerinin atanması özelliğinin, "Java yolu" nun gerektireceği biraz karışık kodun (açıkça özel destek alanları ve alıcı / alıcı işlevleriyle) daha da temizlenmesine yardımcı olduğunu tamamen ve haksızca ekleyeceğim :

// Warning: C# code!
public sealed class ApproximateDuration {
   public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
      LowMilliseconds = lowMilliseconds;
      HighMilliseconds = highMilliseconds;
   }
   public int LowMilliseconds { get; }
   public int HighMilliseconds { get; }
}

İşte yukarıdakilerin bir Java uygulaması:

public final class ApproximateDuration {
  private final int lowMilliseconds;
  private final int highMilliseconds;

  public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
    this.lowMilliseconds = lowMilliseconds;
    this.highMilliseconds = highMilliseconds;
  }

  public int getLowMilliseconds() {
    return lowMilliseconds;
  }

  public int getHighMilliseconds() {
    return highMilliseconds;
  }
}

Şimdi bu oldukça lanet olası. Değişmezliğin çok önemli ve kasıtlı kullanımına dikkat edin - bu, bu özel değer taşıyan sınıf için çok önemli görünmektedir.

Bu nedenle, bu sınıf aynı zamanda structbir değer türü olmak için iyi bir adaydır . Bazı testler, bir yapıya geçmenin bir çalışma zamanı performans avantajı olup olmadığını gösterecektir (olabilir).


9
Sanırım bu benim en sevdiğim cevap. Bunun gibi berbat bir atıcı sınıfını terfi ettirdiğimde, büyümüş bir şeye, ilgili tüm sorumlulukların evi haline gelir ve zing! her şey temizlenir! Ve genellikle aynı zamanda problem alanı hakkında ilginç bir şey öğrenirim. Açıkça görüldüğü gibi, fazla mühendislikten kaçınmak için bir yetenek var ... ama Gosling, GOTO’larda olduğu gibi tuple sözdizimini bıraktığı zaman aptal değildi. Kapanış ifaden mükemmel. Teşekkürler!
SusanW

2
Bu cevabı beğenirseniz, Domain Driven Design hakkında bilgi edinin. Etki alanı kavramlarını kodda temsil etmekle ilgili bu konuda söylenecek çok şey var.
neontapir

@neontapir Yep, teşekkürler! Bunun için bir isim olduğunu biliyordum! :-) Bir etki alanı kavramı organik olarak ilham verici bir yeniden düzenleme işleminden (bu gibi!) Göründüğü zaman tercih etmeliyim de olsa ... Neptün'ü keşfederek 19. yüzyıldaki yerçekimi probleminizi çözdüğünüz zamanki gibi. .
SusanW

@SusanW Katılıyorum. İlkel takıntıyı gidermek için yeniden yapılanma, etki alanı kavramlarını ortaya çıkarmak için mükemmel bir yol olabilir!
neontapir,

Örnek olarak Java versiyonunu tartışıldığı gibi ekledim. C # 'daki sihirli sözdizimsel şekeri bulamadım, bu yüzden eksik bir şey varsa, bana bildirin.
JimmyJames

24

Hem Python hem de Java, tasarımcılarının felsefesine göre sürdürülebilirlik için optimize edilmiştir, ancak bunun nasıl gerçekleştirileceği konusunda çok farklı fikirleri vardır.

Python, kodun açıklığı ve sadeliği için optimize eden çok paradigma bir dildir (okunması ve yazılması kolaydır).

Java (geleneksel olarak) , daha ayrıntılı kodlar pahasına olsa bile, açıklık ve tutarlılık için en iyi duruma getiren tek paradigma sınıfı tabanlı bir OO dilidir .

Bir Python tuple, sabit sayıda alana sahip bir veri yapısıdır. Aynı işlevsellik, açıkça beyan edilmiş alanları olan normal bir sınıfla da sağlanabilir. Python'da, sınıflara alternatif olarak tupler sağlamak doğaldır, çünkü özellikle tuples için yerleşik sözdizimi desteği nedeniyle, kodu büyük ölçüde basitleştirmenize olanak tanır.

Ancak bu, zaten açıkça bildirilen sınıfları kullanabildiğiniz için, bu tür kısayollar sağlamak için Java kültürüyle gerçekten eşleşmiyor. Sadece bazı kod satırlarını kaydetmek ve bazı bildirimlerden kaçınmak için farklı türde bir veri yapısı oluşturmaya gerek yok.

Java, en az özel durum sözdizimsel şekeri ile tutarlı bir şekilde uygulanan tek bir kavramı (sınıfları) tercih ederken, Python, herhangi bir amaç için en uygun olanı seçmenize olanak tanıyan çok sayıda araç ve çok sözdizimsel şeker sağlar.


+1, ama yine de tubaları tanıtma gereği bulan Scala gibi sınıfları olan, statik olarak yazılmış dilleri olan bir örgü nasıl? Sanırım, bazı durumlarda sınıfların olduğu diller için bile, tüller sadece daha temiz bir çözüm.
Andres F.

@AndresF .: Ağ ile neyi kastediyorsunuz? Scala, Java'dan farklı bir dildir ve farklı tasarım ilkeleri ve deyimleri vardır.
JacquesB

"Paragraf kültürü [...]" ile gerçekten uyuşmuyor ”ile başlayan 5. paragrafınıza cevap demek istedim. Aslında, ayrıntıların Java kültürünün bir parçası olduğunu kabul ediyorum, ancak trol eksikliği, "zaten açıkça ilan edilmiş sınıfları kullandıkları" anlamına gelmez - sonuçta, Scala'nın da açık sınıfları vardır (ve daha özlü vaka sınıfları sunar ), fakat Ayrıca tuna izin veriyor! Sonunda, Java'nın sınıflar aynı (daha fazla kargaşayla) başarabildiği için değil, aynı zamanda sözdiziminin C programcılarına aşina olması ve C'nin tekilleri olmaması nedeniyle de tuple tanışmadığını düşünüyorum.
Andres F.

@AndresF .: Scala, birçok şeyi yapmanın yollarından hoşnutsuzluğa sahip değil, hem işlevsel paradigmadan hem de klasik OO'dan gelen özellikleri bir araya getiriyor. Python felsefesine bu şekilde yaklaşıyor.
JacquesB

@AndresF .: Evet Java, C / C ++ 'ya yakın sözdizimi ile başladı, fakat çok basitleştirildi. C #, Java'nın neredeyse kesin bir kopyası olarak başladı, ancak yıllar içinde tekiller dahil birçok özellik ekledi. Bu yüzden dil tasarım felsefesinde gerçekten bir fark var. (Java'nın daha sonraki sürümleri olsa da daha az dogmatik görünmektedir. Yüksek dereceli fonksiyon başlangıçta reddedilmiştir çünkü aynı sınıfları yapabildiniz, fakat şimdi tanıtıldılar. Dolayısıyla belki felsefede bir değişiklik görüyoruz.)
JacquesB

16

Uygulamaları aramayın; En iyi yöntemler KÖTÜ, İYİ desenleri dediği gibi, genellikle kötü bir fikirdir ? . En iyi uygulamaları sormadığını biliyorum, ama hala orada bazı alakalı unsurlar bulacağını düşünüyorum.

Sorununuza bir çözüm aramak pratikten daha iyidir ve Java'nızda üç değeri döndürecek sorununuz yoktur:

  • Diziler var
  • Bir diziyi bir liner'de liste olarak döndürebilirsiniz: Arrays.asList (...)
  • Nesne tarafını daha az kazan plakasıyla (ve lombok olmadan) korumak istiyorsanız:

class MyTuple {
    public final long value_in_milliseconds;
    public final boolean is_in_seconds;
    public final boolean is_under_1ms;
    public MyTuple(long value_in_milliseconds,....){
        ...
    }
 }

Burada yalnızca verilerinizi içeren ve değişmez bir nesneye sahipsiniz ve bu nedenle alıcılara gerek kalmaz. Bununla birlikte, bazı serileştirme araçlarını veya ORM gibi kalıcı bir katman kullanırsanız, genellikle alıcı / ayarlayıcı kullandıklarını (ve alıcı / ayarlayıcı yerine alanları kullanmak için bir parametre kabul edebileceğini) unutmayın. İşte bu yüzden bu uygulamalar çok kullanılıyor. Dolayısıyla, uygulamalar hakkında bilgi edinmek istiyorsanız, neden daha iyi bir kullanım için burada olduklarını anlamak daha iyidir.

Sonunda: alıcıları kullanıyorum çünkü çok fazla serileştirme aracı kullanıyorum, ancak ikisini de yazmıyorum; Lombok kullanıyorum: IDE'nin sağladığı kısayolları kullanıyorum.


9
Fiili bir standart haline geldiklerinden, çeşitli dillerde karşılaştığınız ortak deyimleri anlamada hala değer var. Bu deyimler, herhangi bir nedenden ötürü “en iyi uygulamalar” başlığı altına girme eğilimindedir.
Berin Loritsch

3
Hey, soruna mantıklı bir çözüm. [Gs] etters ile tamamen gelişmiş bir OOP çözümü yerine, birkaç kamu üyesi ile bir sınıf (/ yapı) yazmak oldukça makul görünüyor.
17'de,

3
Bu, şişkinlik yaratır, çünkü Python'dan farklı olarak, arama veya daha kısa ve daha zarif bir çözüm teşvik edilmez. Ancak, tersine, şişirmenin herhangi bir Java geliştiricisi için aşağı yukarı aynı tür olması. Bu nedenle, bir dereceye kadar daha yüksek bir sürdürülebilirlik derecesi vardır, çünkü geliştiriciler daha değiştirilebilirdir ve iki proje bir araya gelirse veya iki ekip istemeden ayrılırsa, mücadele etmek için daha az stil farkı vardır.
Mikhail Ramendik

2
@MikhailRamendik YAGNI ve OOP arasında bir takas. Ortodoks OOP, her bir nesnenin değiştirilebilir olması gerektiğini söylüyor; Önemli olan tek şey nesnenin ortak arayüzüdür. Bu, daha az somut nesnelere ve daha fazla arayüze odaklanan kod yazmanıza olanak sağlar; ve alanlar bir arayüzün parçaları olamayacağından, onları asla ortaya çıkarmazsınız. Bu olabilir uzun vadede korumak için kod çok daha kolay hale. Ek olarak, geçersiz durumları önlemenizi sağlar. Orijinal örneğinizde, hem "<ms" hem de "s" olan ve birbirini dışlayan bir nesneye sahip olmak mümkündür. Bu kötü.
Luaan

2
@PeterMortensen Oldukça ilgisiz şeyler yapan bir Java kütüphanesi. Yine de çok iyi. İşte özellikler
Michael

11

Genel olarak Java deyimleri hakkında:

Java'nın her şey için sınıfları olmasının çeşitli nedenleri vardır. Bilgim devam ettiği sürece, asıl sebep:

Java yeni başlayanlar için öğrenmesi kolay olmalı. Ne kadar açık şeyler olursa, önemli detayları kaçırmak o kadar zor olur. Yeni başlayanların kavraması zor olacak daha az sihir gerçekleşir.


Spesifik örneğinize göre: ayrı bir sınıf için argüman çizgisi şudur: eğer bu üç şey birbiriyle bir değer olarak döndürülmeleri için yeterince güçlü ise, bu "şeyi" adlandırmaya değer. Ve ortak bir şekilde yapılandırılmış olan bir grup şeye bir isim vermek, bir sınıfı tanımlamak anlamına gelir.

Kazan plakasını Lombok gibi aletlerle azaltabilirsiniz:

@Value
class MyTuple {
    long value_in_milliseconds;
    boolean is_in_seconds;
    boolean is_under_1ms;
}

5
Aynı zamanda Google’ın AutoValue projesi: github.com/google/auto/blob/master/value/userguide/index.md
Ivan

Bu, Java programlarının nispeten kolay bir şekilde sürdürülmesinin
Thorbjørn Ravn Andersen

7

Java kültürü hakkında söylenebilecek çok şey var, ancak şu anda karşı karşıya kalmanız durumunda, birkaç önemli husus olduğunu düşünüyorum:

  1. Kütüphane kodu bir kez yazılmıştır ancak çok daha sık kullanılmaktadır. Kütüphane yazma yükünü en aza indirmek güzel olsa da, uzun vadede kütüphane kullanma yükünü en aza indirecek şekilde yazmak daha yararlı olacaktır.
  2. Bu, kendi kendini belgeleyen türlerin harika olduğu anlamına gelir: yöntem adları, neler olduğunu ve bir nesneden ne elde ettiğinizi netleştirmenize yardımcı olur.
  3. Statik yazım, belirli hata sınıflarını ortadan kaldırmak için çok yararlı bir araçtır. Kesinlikle her şeyi çözmez (insanlar Haskell hakkında şaka yapmayı sever, bu da bir kez kodunuzu kabul etmek için tip sistemini aldığınızda, muhtemelen doğrudur), ancak bazı yanlış şeyler yapmayı imkansız hale getirir.
  4. Kütüphane kodu yazmak sözleşmeleri belirtmekle ilgilidir. Argümanınız ve sonuç türleriniz için arayüzler tanımlamak, sözleşmelerinizin sınırlarını daha net bir şekilde tanımlamanızı sağlar. Eğer bir şey bir tuple kabul ederse veya üretirse, gerçekte almanız veya üretmeniz gereken türden bir türe ait değildir ve bu tür bir jenerik tip üzerindeki kısıtlamalar konusunda çok az şey vardır (hatta doğru sayıda elemente sahip mi? beklediğiniz tipte bunlar mı?).

Alanlı "Struct" sınıfları

Diğer cevapların belirttiği gibi, sadece ortak alanlara sahip bir sınıfı kullanabilirsiniz. Bunları final yaparsanız, sabit bir sınıf elde edersiniz ve bunları yapıcı ile başlatırsınız:

   class ParseResult0 {
      public final long millis;
      public final boolean isSeconds;
      public final boolean isLessThanOneMilli;

      public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
         this.millis = millis;
         this.isSeconds = isSeconds;
         this.isLessThanOneMilli = isLessThanOneMilli;
      }
   }

Elbette, bu, belirli bir sınıfa bağlı olduğunuz anlamına gelir ve ayrıştırma sonucu üretmek veya tüketmek için gereken her şeyin bu sınıfı kullanması gerekir. Bazı uygulamalar için sorun değil. Diğerleri için bu bazı ağrılara neden olabilir. Java kodlarının çoğu sözleşmeleri tanımlamakla ilgilidir ve bu genellikle sizi arabirimlere götürür.

Başka bir tuzak, sınıf temelli bir yaklaşımla, alanları ortaya çıkardığınız ve bu alanların hepsinin değerleri olması gerektiğidir. Örneğin, isSeconds ve millis, isLessThanOneMilli doğru olsa bile daima bir değere sahip olmalıdır. LessThanOneMilli doğru olduğunda millis alanının değerinin yorumlanması ne olmalıdır?

Arayüzler olarak "Yapılar"

Arayüzlerde izin verilen statik yöntemlerle, çok fazla sözdizim yükü olmadan değişmez türler oluşturmak oldukça kolaydır. Örneğin, bahsettiğiniz sonuç yapısını şöyle bir şey olarak uygulayabilirim:

   interface ParseResult {
      long getMillis();

      boolean isSeconds();

      boolean isLessThanOneMilli();

      static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
         return new ParseResult() {
            @Override
            public boolean isSeconds() {
               return isSeconds;
            }

            @Override
            public boolean isLessThanOneMilli() {
               return isLessThanOneMill;
            }

            @Override
            public long getMillis() {
               return millis;
            }
         };
      }
   }

Bu hala çok fazla kazan, kesinlikle katılıyorum, ama bunun da birkaç faydası var ve sanırım ana sorularınızın bazılarını cevaplamaya başlıyorlar.

Bu ayrıştırma sonucu gibi bir yapıyla, ayrıştırıcınızın sözleşmesi çok net bir şekilde tanımlanır. Python'da, bir bağ diğer bağdan gerçekten farklı değildir. Java'da statik yazım mevcuttur, bu nedenle zaten bazı hata sınıflarını ekarte ediyoruz. Örneğin, Python'da bir demet döndürüyorsanız ve onu döndürmek istiyorsanız (millis, isSeconds, isLessThanOneMilli), yanlışlıkla yapabilirsiniz:

return (true, 500, false)

ne zaman demek istedin:

return (500, true, false)

Bu tür bir Java arayüzü ile derleyemezsiniz:

return ParseResult.from(true, 500, false);

hiç. Yapmak zorundasın:

return ParseResult.from(500, true, false);

Bu genel olarak statik olarak yazılmış dillerin bir faydasıdır.

Bu yaklaşım aynı zamanda size hangi değerleri alabileceğinizi kısıtlama yeteneği vermeye başlıyor. Örneğin, getMillis () işlevini çağırırken, isLessThanOneMilli () 'nin doğru olup olmadığını kontrol edebilir ve eğer öyleyse, bu durumda millis için anlamlı bir değer olmadığından, bir IllegalStateException (örneğin) atabilirsiniz.

Yanlış İşi Yapmayı Zorlaştırmak

Yukarıdaki arayüz örneğinde, yine de isSeconds ve isLessThanOneMilli argümanlarını yanlışlıkla aynı tipte olduklarından takas etme probleminiz var.

Uygulamada, gerçekten TimeUnit ve süresinden yararlanmak isteyebilirsiniz, böylece şöyle bir sonuç elde edebilirsiniz:

   interface Duration {
      TimeUnit getTimeUnit();

      long getDuration();

      static Duration from(TimeUnit unit, long duration) {
         return new Duration() {
            @Override
            public TimeUnit getTimeUnit() {
               return unit;
            }

            @Override
            public long getDuration() {
               return duration;
            }
         };
      }
   }

   interface ParseResult2 {

      boolean isLessThanOneMilli();

      Duration getDuration();

      static ParseResult2 from(TimeUnit unit, long duration) {
         Duration d = Duration.from(unit, duration);
         return new ParseResult2() {
            @Override
            public boolean isLessThanOneMilli() {
               return false;
            }

            @Override
            public Duration getDuration() {
               return d;
            }
         };
      }

      static ParseResult2 lessThanOneMilli() {
         return new ParseResult2() {
            @Override
            public boolean isLessThanOneMilli() {
               return true;
            }

            @Override
            public Duration getDuration() {
               throw new IllegalStateException();
            }
         };
      }
   }

Bu çok daha fazla kod haline gelmeye başlıyor , ancak yalnızca bir kez yazmanız gerekiyor (ve doğru bir şekilde belgelemiş olduğunuzu varsayarak), kodunuzu kullanarak bitiren kişilerin sonucun ne anlama geldiğini tahmin etmeleri gerekmez. Yanlışlıkla ne result[0]zaman oldukları gibi şeyler yapamıyorum result[1]. Hala olsun oluşturmak oldukça özlü örneklerini ve bunların dışına veri bütün bu zor ya değildir:

  ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
  ParseResult2 y = ParseResult2.lessThanOneMilli();

Bunun yanında sınıf temelli bir yaklaşımla da böyle bir şey yapabileceğinizi unutmayın. Sadece farklı durumlar için yapıcılar belirtin. Yine de diğer alanları neyin başlatacağı konusunda hala sorunlarınız var ve bunlara erişimi engelleyemiyorsunuz.

Bir başka cevap, Java’nın kurumsal tipi niteliğinin, çoğu zaman, zaten var olan diğer kütüphaneleri oluşturduğunuz veya başkalarının kullanması için kütüphaneler yazdığınız anlamına gelir. Sizin genel API bunun önlenebilir eğer sonuç türlerini deşifre belgeleri danışmanlık çok zaman gerektirmemelidir.

Yalnızca yazma kez bu yapıları, ancak oluşturmak onlara defalarca, bu nedenle hala (Alacağınız) özlü oluşturulmasını istiyoruz. Statik yazım, bunlardan elde ettiğiniz verilerin beklediğiniz gibi olmasını sağlar.

Şimdi, tüm söylenenler, basit tote veya listelerin çok fazla anlam ifade edebilecekleri yerler var. Bir şeyin dizisini döndürmede daha az ek yük olabilir ve eğer durum buysa (ve bu ek yük önemli, profillemeyle belirleyecekseniz), o zaman içten basit bir değer dizisi kullanmak çok mantıklı olabilir. Genel API’niz hala açıkça tanımlanmış türlere sahip olmalıdır.


Sonunda TimeUnit yerine kendi enum kullandım, çünkü gerçek zaman birimlerinin yanında UNDER1MS'i ekledim. Şimdi sadece üç alanım var, üç değil. (Teoride, TimeUnit.NANOSECONDS'yi kötüye kullanabilirdim, ama bu çok kafa karıştırıcı olurdu.)
Mikhail Ramendik

Bir alıcı yaratmadım, ama şimdi originalTimeUnit=DurationTimeUnit.UNDER1MSarayan kişi milisaniye değerini okumayı denerse bir alıcının istisna atmama nasıl izin vereceğini görüyorum .
Mikhail Ramendik

Bahsettiğinizi biliyorum ama python tuples ile olan örneğinizin dinamik ve statik olarak yazılmış tuples ile ilgili olduğunu unutmayın; Gerçekten de tekiller hakkında pek bir şey ifade etmiyor. Sen olabilir statik :) Ben eminim biliyorsunuz, derlemek başarısız olur dizilerini yazdığınız
Andres F.

1
@Veedrac Ne demek istediğinden emin değilim. Benim nokta daha fazla zaman orada harcanacak beri (daha uzun sürer olsa bile) Bunun gibi daha verbosely kütüphane kod yazmak Tamam olmasıydı kullanarak daha kod yazma bunu.
Joshua Taylor,

1
@Veedrac Evet, bu doğru. Ama orada iddia ediyorum olduğu anlamlı yeniden kullanılacaktır kodu ve kod arasında bir ayrım olduğunu olmaz. Örneğin, bir Java paketinde, bazı sınıflar halka açıktır ve diğer konumlardan alışırlar. Bu API'ler iyi düşünülmelidir. Dahili olarak, sadece birbirleriyle konuşması gereken sınıflar var. Bu iç bağlantılar, hızlı ve kirli yapıların (örneğin, üç öğeye sahip olması beklenen bir Nesne []) iyi olabileceği yerlerdir. Genel API için, daha anlamlı ve açık bir şey genellikle tercih edilmelidir.
Joshua Taylor,

7

Sorun, elmaları portakallarla karşılaştırmanız . Tek bir değerden daha fazla geri dönüşün nasıl yazılacağını sordunuz ve yazılmamış tuple ile hızlı ve kirli bir python örneği verdiniz ve pratikte tek satırlık bir cevap aldınız .

Kabul edilen cevap doğru bir iş çözümü sunar. İlk kez atmak ve doğru bir şekilde uygulamak zorunda kalacağınız geçici bir geçici çözüm yok. İade edilen değerle ilgili pratik bir şey yapmanız gerekir; enstrümantasyon ve mümkün olan her şey.

Bu da hiç uzun değil. Yazmanız gereken tek şey alan tanımlarıdır. Setörler, alıcılar, hashCode ve eşittir üretilebilir. Bu yüzden, asıl sorunuz, alıcıların ve belirleyicilerin neden otomatik olarak oluşturulmadığı, ancak sözdizimi meselesi (sözdizimsel şeker sorunu, bazılarının söyleyeceği) kültürel mesele değil.

Ve son olarak, konum overthinking hiç önemli olmayan bir şey hızlandırmak için çalışıyor. DTO dersleri yazmak için harcanan zaman, sistemi korumak ve hata ayıklamak için harcanan zamana kıyasla önemsizdir. Bu nedenle hiç kimse daha az ayrıntı için optimize edemez.


2
"Hiç kimse" aslında yanlıştır - daha az ayrıntı için en iyi duruma getiren araçların bir sürü vardır, düzenli ifadeler aşırı bir örnektir. Fakat "ana Java dünyasında kimse yok" anlamına gelebilir ve bu cevabı okumak çok mantıklı geliyor, teşekkürler. Ayrıntılarla ilgili kendi endişem kod yazmak için harcanan zaman değil, onu okumak için harcanan zamandır; IDE okuma zaman kazandırmaz; fakat fikir, görünen o ki, yalnızca birinin arayüz tanımını okuduğu zamanların% 99'unun özlü olması gerektiği mi?
Mikhail Ramendik

5

Gördüklerinize katkıda bulunan üç farklı faktör vardır.

Tuples, adlandırılmış alanlara karşı

Belki de en önemsiz - diğer dillerde bir demet kullandınız. Tuplelerin iyi bir fikir olup olmadığını tartışmak gerçekten önemli değil - ama Java'da daha ağır bir yapı kullandınız, bu yüzden biraz haksız bir karşılaştırma yaptınız: bir dizi nesne ve bir çeşit döküm kullanmış olabilirsiniz.

Dil sözdizimi

Sınıfı ilan etmek daha kolay olabilir mi? Alanları halka açık yapmaktan veya bir harita kullanmaktan bahsetmiyorum ama Scala'nın vaka sınıfları gibi, tanımladığınız kurulumun tüm faydalarını sağlayan ama çok daha özlü bir şey:

case class Foo(duration: Int, unit: String, tooShort: Boolean)

Bunu yapabilirdik - ama bunun bir bedeli var: sözdizimi daha karmaşık hale geliyor. Elbette bazı durumlar için, hatta çoğu durumda, hatta önümüzdeki 5 yıl boyunca çoğu durumda bile buna değer olabilir - ancak yargılanması gerekir. Bu arada, bu, kendiniz değiştirebileceğiniz dillerle (yani lisp) güzel şeylerle ilgilidir - ve sözdiziminin basitliği nedeniyle bunun nasıl mümkün olacağını not edin. Dili gerçekten değiştirmeseniz bile, basit bir sözdizimi daha güçlü araçlar sağlar; örneğin, çoğu zaman Java için mevcut olan bazı yeniden düzenleme seçeneklerini özlüyorum, ancak Scala için değil.

Dil felsefesi

Ancak en önemli faktör, bir dilin belirli bir düşünce tarzını mümkün kılması gerektiğidir. Bazen baskıcı hissedebilir (genellikle belirli bir özelliği desteklemeyi diledim) ancak özellikleri kaldırmak da bu özelliklere sahip olmak kadar önemlidir. Her şeyi destekleyebilir misin? Elbette, fakat o zaman her dili bir araya getiren bir derleyici de yazabilirsiniz. Başka bir deyişle, bir diliniz olmayacak - bir dil süpersetine sahip olacaksınız ve her proje temel olarak bir alt kümeyi benimseyecekti.

Elbette dilin felsefesine aykırı kod yazmak mümkündür ve gözlemlediğiniz gibi sonuçlar genellikle çirkindir. Java'da sadece birkaç alana sahip bir sınıfa sahip olmak, Scala'da bir değişken kullanmaya benzer, yozlaştırıcı prolog fonksiyonlara işaret eder, haskell'de güvensiz bir Performans yapmayı vb. Yapar. Java sınıfları hafif olmak zorunda değildir. . Bir şey zor göründüğünde, geri çekilmek ve başka bir yol olup olmadığını görmek genellikle verimlidir. Örnekte:

Neden süre birimlerden ayrı? Bir süre bildirmenize izin veren bol miktarda zaman kütüphanesi vardır - Süre (5, saniye) gibi bir şey (sözdizimi değişecektir), o zaman istediğinizi çok daha sağlam bir şekilde yapmanıza izin verir. Belki bunu dönüştürmek istersiniz - neden [1] (veya [2]?) Sonucunun 'saat' olduğunu ve 3600 ile çarpıp çarpmadığını kontrol edin. Ve üçüncü argüman için - amacı nedir? Bir noktada "1ms'den daha az" veya gerçek zamanı yazdırmanız gerekeceğini tahmin ediyorum - bu doğal olarak zaman verilerine ait olan bir mantık. Yani böyle bir sınıfa sahip olmalısınız:

class TimeResult {
    public TimeResult(duration, unit, tooShort)
    public String getResult() {
        if tooShort:
           return "too short"
        else:
           return format(duration)
}

}

ya da verilerle ne yapmak istiyorsan, mantığı içine al.

Elbette, bu yolun işe yaramayacağı bir durum olabilir - Bunun, sonuç sonuçlarını aptalca Java koduna dönüştürmenin sihirli algoritması olduğunu söylemiyorum! Ve çok çirkin ve kötü olduğu durumlar olabilir ve belki de farklı bir dil kullanmalıydınız - sonuçta bu kadar çok var!

Ancak Java’daki sınıfların neden “ağır yapılar” olduğuna dair görüşüm, onları veri kapları olarak değil, kendi kendine yeten mantık hücreleri olarak kullanmanız gerektiğidir.


Sürelerle yapmak istediğim şey aslında bir miktar toplamayı yapmak ve sonra başka bir şeyle karşılaştırmak. Çıktı yok. Karşılaştırma dikkate alınmalıdır, bu yüzden karşılaştırma sırasında orijinal zaman birimlerini bilmem gerekir.
Mikhail Ramendik

Muhtemelen sınıfımın örneklerini eklemek ve karşılaştırmak için bir yol oluşturmak mümkün olurdu, ama bu çok ağır olabilir. Özellikle de, yüzer olarak bölmem ve çoğaltmam gerektiğinden (bu Kullanıcı Arabiriminde doğrulamak için yüzdeler var). Şimdilik final alanlarıyla, güzel bir uzlaşmaya benzeyen değişmez bir sınıf yaptım.
Mikhail Ramendik

Bu özel soru ile ilgili daha önemli kısım, olayların daha genel yanıydı ve tüm cevaplardan bir resmin bir araya geldiği görülüyor.
Mikhail Ramendik

@MikhailRamendik belki bir akümülatör çeşidi bir seçenek olabilir - fakat sorunu daha iyi biliyorsunuzdur. Aslında, bu belirli sorunu çözmekle ilgili değil, eğer biraz gergilerim kırılırsa özür dilerim - asıl amacım, dilin "basit" veri kullanımını engellediği yönünde. bazen bu, yaklaşımınızı yeniden düşünmenize yardımcı olur - diğer zamanlarda bir int sadece bir
intdir

@MikhailRamendik Kazan düzlemi yolunun güzel tarafı, bir sınıfınız olduğunda, ona davranış ekleyebilmenizdir. Ve / veya yaptığınız gibi değiştirilemez hale getirin (bu çoğu zaman iyi bir fikirdir; her zaman yeni bir nesneyi toplam olarak geri getirebilirsiniz). Sonunda, "gereksiz" sınıfınız ihtiyacınız olan tüm davranışı içine alabilir ve ardından alıcılar için maliyet göz ardı edilebilir. Ayrıca, onlara ihtiyacınız olabilir veya gerekmeyebilir. Lombok veya autovalue kullanmayı düşünün .
maaartinus

5

Anladığım kadarıyla, temel nedenler

  1. Arayüzler, sınıfları soyutlamanın temel Java yoludur.
  2. Java yalnızca bir yöntemden tek bir değer döndürebilir - bir nesne veya bir dizi veya yerel değer (int / long / double / float / boolean).
  3. Arabirimler alanlar içeremez, sadece yöntemler. Bir alana erişmek istiyorsanız, bir yöntemden geçmelisiniz - dolayısıyla alıcılar ve belirleyiciler.
  4. Bir yöntem bir arayüz döndürürse, gerçekte döndürmek için bir uygulayıcı sınıfınız olmalıdır.

Bu size "sıra dışı bir sonuç için geri dönmek için bir sınıf yazmalısınız" anlamına gelir ki bu da oldukça ağırdır. Arabirim yerine bir sınıf kullandıysanız, yalnızca alanlara sahip olabilir ve doğrudan kullanabilirsiniz, ancak bu sizi belirli bir uygulamaya bağlar.


Bu eklemek için: eğer sen sadece - ideal olarak değişmez (diyelim bir sınıfta özel yöntem) küçük bir bağlamda bu ihtiyaç, bir çıplak rekor kullanmanız iyi olur. Fakat bu gerçekten gerçekten gerçekten sınıfın kamu sözleşmesine sızmamamalı (hatta daha da kötüsü, kütüphane!). Elbette gelecekteki kod değişikliklerinde bunun asla gerçekleşmemesini sağlamak için kayıtlara karşı karar verebilirsiniz; bu genellikle daha basit bir çözümdür.
Luaan

Ve "Tuples Java'da iyi çalışmıyor" görüşüne katılıyorum. Jenerik kullanıyorsanız, bu çok hızlı bir şekilde kötü sonuçlanacaktır.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Genel olarak jenerik ve tupel içeren, statik olarak yazılmış bir dil olan Scala'da iyi çalışıyorlar . Yani belki bu Java'nın suçu?
Andres F.

@AndresF. Lütfen "Eğer jenerik kullanıyorsanız" bölümünü dikkate alınız. Java bunu yapar, örneğin Haskell'ın aksine karmaşık yapılara borç vermez. Scala'yı bir karşılaştırma yapacak kadar iyi tanımıyorum, ancak Scala'nın birden fazla getiri değeri sağladığı doğru mu? Bu oldukça yardımcı olabilir.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Scala işlevleri, bir tuple türünde olabilen tek bir dönüş değerine sahiptir (örneğin def f(...): (Int, Int), bir ftamsayı dizisi olan bir değer döndüren bir işlevdir ). Java’daki jeneriklerin bununla ilgili olduğundan emin değilim; Haskell'in ayrıca, örneğin silme işlemi yaptığını da unutmayın. Java'nın perdeleri olmamasının teknik bir nedeni olduğunu sanmıyorum .
Andres F.

3

JacquesB’nin cevabını kabul ediyorum

Java (geleneksel olarak) açıklık ve tutarlılık için en iyi duruma getirme sağlayan tek paradigma sınıfı tabanlı bir OO dilidir.

Ancak açıklık ve tutarlılık, optimize edilmesi gereken son hedefler değildir. 'Python okunabilirlik için optimize edilmiştir' dediğinizde, derhal hedefin 'sürdürülebilirlik' ve 'gelişim hızı' olduğunu söylersiniz.

Java yöntemini kullanırken açıklık ve tutarlılık olduğunda ne elde edersiniz? Benim görüşüme göre, herhangi bir yazılım problemini çözmek için öngörülebilir, tutarlı ve tek tip bir yol sağladığını iddia eden bir dil olarak gelişti.

Başka bir deyişle, Java kültürü yöneticilerin yazılım geliştirmeyi anladığına inanmalarını sağlamak için optimize edilmiştir.

Veya olarak bir bilge adam çok uzun zaman önce koymak ,

Bir dili yargılamanın en iyi yolu, savunucuları tarafından yazılan koda bakmaktır. "Radix enim omnium malorum est cupiditas" - ve Java açıkça para yönelimli bir programlama (MOP) örneğidir. SGI’de Java’nın baş savunucusu bana şöyle dedi: “Alex, paranın olduğu yere gitmelisin.” Ama özellikle paranın olduğu yere gitmek istemiyorum - genellikle orada güzel kokmuyor.


3

(Bu cevap özellikle Java için bir açıklama değildir, bunun yerine “Ağır [uygulamalar] ne için optimize edilebilir?” Sorusuna genel bir cevap verir.)

Bu iki prensibi göz önünde bulundurun:

  1. Programınız doğru olanı yaptığında iyidir. Doğru şeyi yapan programları yazmayı kolaylaştırmalıyız.
  2. Programın yanlış bir şey yapması kötü. Yanlış olan programları yazmayı zorlaştırmalıyız.

Bu hedeflerden birini optimize etmeye çalışmak bazen diğerinin önüne geçebilir (yani yanlış olanı yapmayı zorlaştırmak, doğru olanı yapmayı zorlaştırabilir veya bunun tersi de olabilir).

Herhangi bir özel durumda hangi takaslar yapılır, uygulamaya, söz konusu programcıların veya ekibin kararlarına ve kültürüne (kurum veya dil topluluğunun) bağlıdır.

Örneğin, programınızdaki bir hata veya birkaç saatlik kesinti, can kaybına (tıbbi sistemler, havacılık) veya hatta yalnızca paraya (Google’ın reklam sistemlerinde milyonlarca dolar gibi) yol açabileceği takdirde, farklı değişimler yaparsınız (değil Sadece kendi dilinizde değil, aynı zamanda mühendislik kültürünün diğer yönlerinde) bir kerelik senaryo için yaptığınızdan daha fazla: muhtemelen "ağır" tarafa doğru eğimli.

Sisteminizi daha "ağır" yapma eğiliminde olan diğer örnekler:

  • Uzun yıllar boyunca birçok ekip tarafından yürütülen büyük bir kod temeli varsa, büyük bir endişe, bir başkasının API'sini yanlış kullanabilmesidir. Yanlış sırayla argümanlarla çağrılan veya beklediği bazı önkoşulları / kısıtlamaları sağlamadan çağrılan bir işlev felaket olabilir.
  • Bunun özel bir örneği olarak, ekibinizin belirli bir API veya kütüphane bulundurduğunu ve onu değiştirmek veya yeniden düzenlemek istediğini söyleyin. Kullanıcılarınız kodunuzu nasıl kullanacakları konusunda “kısıtlı” olduklarında, onu değiştirmek o kadar kolay olur. (Burada, hiç kimsenin alışılmadık bir şekilde kullanamayacağına dair gerçek güvencelere sahip olmanın tercih edileceğini unutmayın.)
  • Geliştirme birden fazla kişi veya ekip arasında paylaştırılmışsa, bir kişinin veya ekibin arayüzü “belirtmesi” ve başkalarının uygulamayı gerçekleştirmesi iyi bir fikir olabilir. Çalışması için, uygulama tamamlandığında, uygulamanın gerçekten şartnameye uyduğuna dair bir dereceye kadar güven kazanmanız gerekir.

Bunlar sadece bazı şeylerdir, size “ağır” hale getirmenin (ve çabucak bazı kodları yazmanızın zorlaştırılması) gerçekten kasıtlı olabileceği durumlar hakkında bir fikir vermek için. (Hatta eğer kod yazmak çok fazla çaba gerektiriyorsa, kod yazmadan önce daha dikkatli düşünmenizi sağlayabilir! Elbette bu tartışma dizisi hızlı bir şekilde gülünç olur.)

Bir örnek: Google’ın dahili Python sistemi, işleri başka birinin kodunu basitçe içe aktaramayacak şekilde “ağır” hale getirme eğilimindedir; bağımlılığı, almak istediğiniz kodun, kütüphanesini kendi kütüphanesi olarak bildirmesi gereken bir BUILD dosyasına bildirmeniz gerekir. kodunuza görünür, vb.


Not : Yukarıdakilerin tümü, olayların “ağır” olma eğiliminde olduğu zamanlar hakkındadır. Kesinlikle yok değil iddia Java veya Python (ya diller kendileri veya onların kültürlerini) herhangi bir durum için en uygun olarak değişiklikler yapmak; Bunun hakkında düşünmek için. Bu tür tradeofflarda ilgili iki link:


1
Ya kod okuma ? Orada başka bir tradeoff var. Tuhaf gelişmeler ve ağır ritüellerin ağırlıklandırdığı kodları okumak zordur; IDE'nizde boyler ve gereksiz metin (ve sınıf hiyerarşileri!) ile kodun gerçekte ne yaptığını görmek zorlaşır. Kodun yazılması kolay olması gerektiği (onunla aynı fikirde olup olmadığımdan emin değilim, ancak bazı değerleri vardır) argümanını görebiliyorum, ancak kesinlikle okunması kolay olmalı . O ama oldu - Açıkçası karmaşık kod can ve Java ile inşa edilmiştir - aksi iddia aptalca olurdu sayesinde kendi ayrıntı, ya içinde rağmen bunun?
Andres F.

@AndresF. Cevabı, özellikle Java ile ilgili olmadığını (ki ben büyük bir hayranı değilim) daha net hale getirmek için değiştirdim. Ama evet, kodu okurken tradeoffings: bir ucunda dediğiniz gibi “kodun gerçekte ne yaptığını” kolayca okuyabilmek istiyorsunuz. Diğer taraftan, aşağıdaki gibi soruların cevaplarını kolayca görebilmek istersiniz: bu kodun diğer kodlarla ilişkisi nedir? Bu kodun yapabileceği en kötü şey nedir: etkisi diğer hallerde ne olabilir, yan etkileri nelerdir? Bu kod hangi dış faktörlere bağlı olabilir? (Tabii ki ideal olarak her iki soru grubuna da hızlı cevaplar istiyoruz.)
ShreevatsaR

2

Java kültürü zaman içinde hem açık kaynaklı hem de kurumsal yazılım altyapısından gelen ağır etkilerle gelişti. Bu gerçekten düşünürseniz garip bir karışım. Kurumsal çözümler ağır araçlar gerektirir ve açık kaynak basitlik ister. Sonuçta, Java ortada bir yerde olmasıdır.

Tavsiyeyi etkileyen şeyin bir kısmı Python ve Java’da okunabilir ve sürdürülebilen şeylerin çok farklı olmasıdır.

  • Python'da, dil bir dil özelliğidir.
  • Hem Java hem de C # 'da, demet bir kütüphane özelliğidir (veya olabilir) .

Ben sadece C # 'dan bahsediyorum çünkü standart kütüphanede bir dizi <A, B, C, .. n> sınıfı var ve eğer dil onları doğrudan desteklemiyorsa, hantal tuples'ın mükemmel bir örneği. Neredeyse her durumda, konuyla başa çıkmak için iyi bir sınıf seçtiyseniz, kodunuz daha okunaklı ve bakımı kolay hale gelir. Bağlantılı Yığın Taşması sorunuzdaki spesifik örnekte, diğer değerler, dönüş nesnesinde hesaplanan alıcılar olarak kolayca ifade edilebilir.

Mutlu bir orta yol sağlayan C # platformunun yaptığı ilginç bir çözüm, bu kaşıntıyı oldukça iyi çizen anonim nesnelerin ( C # 3.0'da yayımlanan) fikridir . Ne yazık ki, Java henüz bir eşdeğeri yok.

Java'nın dil özellikleri değiştirilinceye kadar en okunaklı ve bakımı kolay çözüm özel bir nesneye sahip olmaktır. Bu, 1995’teki başlangıcına dayanan dilde ki kısıtlamalardan kaynaklanıyor. Orijinal yazarlar, daha önce hiç yapmayan birçok dil özelliğine sahipti ve geriye dönük uyumluluk, zaman içinde Java’nın evrimini çevreleyen ana kısıtlardan biri.


4
C # 'nın son versiyonunda yeni tuples, kütüphane dersleri yerine birinci sınıf vatandaşlardır. Oraya ulaşmak için çok fazla sürüm aldı.
Igor Soloydenko

Son 3 paragraf, "Dil X, Java'nın yapmadığı bir özelliğe sahiptir" şeklinde özetlenebilir ve cevaba ne eklediklerini göremiyorum. Neden Python'da C # ile Java konusuna değiniyoruz? Hayır, sadece noktayı göremiyorum. 20k + rep'den biraz garip.
Olivier Grégoire

1
Dediğim gibi, "Sadece C # 'dan bahsediyorum çünkü Tuples bir kütüphane özelliğidir", eğer ana kütüphanede Tuples olsaydı, Java’da çalışmasına benzerdi. Kullanması çok hoş değil ve daha iyi cevap neredeyse her zaman özel bir sınıfa sahip olmaktır. Anonim nesneler, toteların ne için tasarlandıklarına benzer bir kaşıntı çiziyor. Daha iyi bir şey için dil desteği olmadan, esasen en iyi korunabilir olanla çalışmak zorundasınız.
Berin Loritsch

@ Igloydenko, belki Java 9 için umut var? Bu, lambdaların bir sonraki adımı olan mantıklı olan özelliklerden sadece bir tanesi.
Berin Loritsch

1
@IgorSoloydenko Oldukça doğru olduğundan emin değilim (birinci sınıf vatandaşlar) - onlar, CLR'de sınıf, yapı, enum gibi birinci sınıf desteğe sahip olmak yerine, kütüphaneden sınıf örnekleri oluşturmak ve bunları açmak için sözdizimi uzantıları gibi görünüyorlar.
Pete Kirkham

0

Bence bu durumda bir sınıf kullanma ile ilgili en temel konulardan biri birlikte gidenlerin birlikte kalması gerektiğidir.

Bu tartışmayı yöntem argümanları hakkında tam tersi bir şekilde yaptım: BMI'yi hesaplayan basit bir yöntem düşünün:

CalculateBMI(weight,height)
{
  System.out.println("BMI: " + (( weight / height ) x 703));
}

Bu durumda bu stile karşı çıkarım çünkü ağırlık ve boy ilişkilidir. Bu yöntem, olmadıklarında iki ayrı değer olan "iletişim kurar" Bir kişinin ağırlığını ve bir başkasının boyunu gösteren bir BMI'yi ne zaman hesaplarsınız? Mantıklı olmaz.

CalculateBMI(Person)
{
  System.out.println("BMI: " + (( Person.weight / Person.height ) x 703));
}

Çok daha mantıklı çünkü şimdi boy ve kilonun aynı kaynaktan geldiğini açıkça belirtiyorsunuz.

Aynı şey birden fazla değer döndürmek için de geçerlidir. Açıkça bağlanmışlarsa, düzgün bir küçük paket döndürün ve birden fazla değer döndürmezse bir nesne kullanın.


4
Tamam, ancak bir grafik göstermek için (varsayımsal) bir göreviniz olduğunu varsayalım: BMI, belirli bir sabit yükseklik için ağırlığa nasıl bağlıdır? Sonra bunu, her veri noktası için bir Kişi oluşturmadan yapamazsınız. Ve, uç noktalara götürürseniz, geçerli doğum tarihi ve pasaport numarası olmadan bir Person sınıfı örneği oluşturamazsınız. Şimdi ne olacak? Orada, Btw downvote vermedi olan bazı sınıfta birlikte özelliklerini donatılacak için geçerli nedenler.
artem

1
@artem, arayüzlerin devreye girdiği bir sonraki adımdır. Yani bir insan boy boyu arayüzü böyle bir şey olabilir. Birlikte gidenlerin birlikte olması gerektiğine işaret etmek için verdiğim örnekte yakalanıyorsunuz.
Pieter B,

0

Dürüst olmak gerekirse, kültür Java programcılarının başlangıçta Nesne Yönelimli ilkelerin ve sürdürülebilir yazılım tasarım ilkelerinin öğretildiği üniversitelerden çıkma eğiliminde olmalarıdır.

ErikE'nin cevabında daha fazla kelime söylediği gibi, sürdürülebilir kod yazmıyor gibisiniz. Örneğinizden gördüğüm kadarıyla, çok garip bir karışıklık endişesi var.

Java kültüründe, hangi kitaplıkların mevcut olduğunun farkında olmaya meyilli olacaksınız ve bu size hazır programlamadan daha fazlasını elde etmenizi sağlayacaktır. Bu nedenle, zorlu endüstriyel ortamlarda denenmiş ve test edilmiş tasarım desenleri ve stilleri için kendi özniteliklerinizi ortaya çıkarmış olacaksınız.

Ancak dediğiniz gibi, bu durumun dezavantajı yok: bugün, 10 yıldan fazla bir süredir Java kullanıyorsanız, hem daha hızlı gelişime olanak tanıyan hem de mikro hizmet tarzı mimarilerle yeni projeler için Node / Javascript veya Go kullanma eğilimindeyim. genellikle yeterli. Google’ın ilk önce Java’yı yoğun bir şekilde kullandığı, ancak Go’nun kaynağı olduğu gerçeğine bakılırsa, aynı şeyi yapıyor olabilirler. Fakat şimdi Go ve Javascript'i kullanmama rağmen, yıllarca Java'yı kullanıp anladığımdan beri edindiğim tasarım becerilerinin çoğunu kullanıyorum.


1
Özellikle, google'ın kullandığı foobar işe alım aracı iki dilin çözümlerde kullanılmasına izin veriyor: java ve python. Go'nun bir seçenek olmadığını ilginç buluyorum. Go'nun bu sunumunda gerçekleştim ve Java ve Python hakkında (bazı diğer dillerin yanı sıra) bazı ilginç noktalara değindim. Örneğin, öne çıkan bir kurşun noktası var: "Belki de sonuçta, uzman olmayan programcıların kafası karışmış" yorum ve dinamik yazarak kullanın. Okunmaya değer.
JimmyJames

@JimmyJames, “uzmanların ellerinde harikalar” diyerek slayda geçti - sorudaki sorunun iyi bir özeti
Tom
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.