Bir yapıcıdaki bir süre (gerçek) döngüsü neden gerçekten bozuk?


47

Genel bir soru olsa da, kapsamım C # 'dır, çünkü C ++ gibi dillerin yapıcı yürütme, bellek yönetimi, tanımsız davranış vb.

Biri bana kolay cevaplanamayan ilginç bir soru sordu.

Neden bir sınıfın kurucusunun hiç bitmeyen bir döngü başlatması (yani oyun döngüsü) için kötü bir tasarım olarak kabul edilir (veya hiç?)?

Bundan kırılan bazı kavramlar var:

  • En az şaşkınlık ilkesi gibi, kullanıcı yapıcının böyle davranmasını beklememektedir.
  • Ünite testleri, bu sınıfı oluşturamayacağınız veya asla döngüden çıkmadığı için enjekte edemediğiniz için daha zordur.
  • Döngünün sonu (oyun sonu) o zaman kavramsal olarak kurucunun bittiği zamandır, ki bu da gariptir.
  • Teknik olarak, böyle bir sınıfın kurucu dışında hiçbir ortak üyesi yoktur, bu da anlaşılmasını zorlaştırır (özellikle uygulamanın olmadığı diller için)

Ve sonra teknik sorunlar var:

  • Yapıcı aslında hiç bitmez, peki burada GC ile ne olur? Bu nesne zaten Gen 0'da mı?
  • Temel bir kurucunun asla geri dönmemesi nedeniyle, böyle bir sınıftan türetmek imkansız ya da en azından çok karmaşık

Böyle bir yaklaşımla açıkça daha kötü veya aldatıcı bir şey var mı?


61
Bu neden iyi? Ana döngünüzü bir yönteme (çok basit yeniden düzenleme) basitçe hareket ettirirseniz, kullanıcı şu şekilde şaşırtıcı olmayan bir kod yazabilir:var g = new Game {...}; g.MainLoop();
Brandin

61
Sorunuz zaten 6 nedenlerini belirten değil kullanmak, ve ben onun lehine tek tek görmek istiyorum. Ayrıca bir koymak için kötü bir fikir neden sorabilirsiniz while(true)bir özellik setter döngü: new Game().RunNow = true?
Groo

38
Aşırı analoji: neden Hitler’in yaptığı şeyin yanlış olduğunu söylüyoruz? Irk ayrımcılığı dışında, İkinci Dünya Savaşı'nı başlatmak, milyonlarca insanı öldürmek, şiddet [vb. + Diğer nedenlerden ötürü] yanlış bir şey yapmadı. Bir şeyin neden yanlış olduğuna dair keyfi bir sebep listesini kaldırırsanız, kelimenin tam anlamıyla bir şey için o şeyin iyi olduğu sonucuna varabilirsiniz.
Bakuriu

4
Çöp toplama (GC) ile ilgili olarak (teknik sorununuz), bu konuda özel bir şey olmazdı. Örnek, yapıcı kodu gerçekten çalıştırılmadan önce var. Bu durumda, örneğe hala ulaşılabilir durumda, bu nedenle GC tarafından talep edilemez. Eğer GC devreye girerse, bu örnek hayatta kalacaktır ve GC ayarının her meydana gelmesinde, nesne her zamanki kurala göre gelecek nesillere tanıtılacaktır. GC olup olmadığı, yeni nesnelerin oluşturulup oluşturulmamasına (yeterince) bağlıdır.
Jeppe Stig Nielsen

8
Bir adım daha ileri gideceğim ve nerede kullanıldığına bakılmaksızın (gerçek) sürenin kötü olduğunu söyleyeceğim. Döngüyü durdurmak için net ve temiz bir yol olmadığı anlamına gelir. Örnekte, oyundan çıkıldığında döngü durmamalı mı, yoksa odağı kaybetmemeli mi?
Jon Anderson,

Yanıtlar:


250

Bir yapıcının amacı nedir? Yeni inşa edilmiş bir nesneyi döndürür. Sonsuz bir döngü ne yapar? Asla geri dönmez. Yapıcı, hiç geri dönmezse, yeni oluşturulmuş bir nesneyi nasıl döndürür? Yapamaz.

Sonsuz bir döngü olan Ergo, bir yapıcının temel sözleşmesini ihlal eder: bir şey inşa etmek.


11
Belki de ortada bir mola vererek (doğru) iken koymak istediler? Riskli, ancak okunaklı.
John Dvorak

2
Katılıyorum, bu doğru - en azından. akademik açıdan. Aynı şey döndüren her işlev için geçerlidir.
Doktor Brown,


5
@JanDvorak: Bu farklı bir soru. OP açıkça "asla bitmeyen" olarak belirtti ve bir olay döngüsünden bahsetti.
Jörg W Mittag

4
@ JörgWMittag iyi, o zaman, evet, bunu bir kurucuya koyma.
John Dvorak

45

Böyle bir yaklaşımla açıkça daha kötü veya aldatıcı bir şey var mı?

Evet tabi ki. Gereksiz, beklenmedik, işe yaramaz, unelegant. Modern sınıf tasarım kavramlarını ihlal eder (uyum, eşleşme). Metod sözleşmesini ihlal eder (bir yapıcının tanımlanmış bir işi vardır ve sadece rastgele bir yöntem değildir). Bu kesinlikle iyi bir şekilde sürdürülemiyor, gelecekteki programcılar, neler olduğunu anlamaya çalışmak ve neden böyle yapılmasının nedenlerini tahmin etmek için çok zaman harcayacaklar.

Bunların hiçbiri, kodunuzun çalışmadığı anlamında bir "hata" dır. Ancak, uzun vadede kodun sürdürülmesini zorlaştırarak (yani, testler eklemek zor, yeniden kullanmak zor, hata ayıklamak zor, uzatmak zor vb.), Büyük ikincil maliyetlere (başlangıçta kodu yazma maliyetine bağlı olarak) neden olacaktır. .).

Yazılım geliştirme yöntemlerinde birçok / en modern geliştirme, yazılımın gerçek yazma / test etme / hata ayıklama / bakım işlemlerini kolaylaştırmak için özel olarak yapılır. Bunların hepsi, kodun rastgele yerleştirildiği "işe yaradığı için" böyle şeylerle çevreleniyor.

Ne yazık ki, düzenli olarak tüm bunlardan tamamen habersiz programcılar ile tanışacaksınız. Çalışıyor, işte bu.

Bir analoji ile bitirmek için (başka bir programlama dili, işte eldeki sorun hesaplamaktır 2+2):

$sum_a = `bash -c "echo $((2+2)) 2>/dev/null"`;   # calculate
chomp $sum_a;                         # remove trailing \n
$sum_a = $sum_a + 0;                  # force it to be a number in case some non-digit characters managed to sneak in

$sum_b = 2+2;

İlk yaklaşımda yanlış olan ne? Makul bir süre sonra 4 döndürür; bu doğru. İtirazlar (bir geliştiricinin onları çürütmek için verebileceği tüm genel nedenlerle birlikte):

  • Okuması daha zor (ama hey, iyi bir programcı onu kolayca okuyabilir ve biz her zaman böyle yaptık; eğer istersen onu bir metoda dönüştürebilirim!)
  • Daha yavaş (ancak bu zamanlama açısından kritik bir yerde değil, bu nedenle bunu görmezden gelebiliriz ve bunun yanında, yalnızca gerektiğinde optimize etmek en iyisidir!)
  • Çok daha fazla kaynak kullanıyor (yeni bir işlem, daha fazla RAM vb. - ancak dito, sunucumuz yeterince hızlıdır)
  • Bağımlılıklarını tanıtır bash(ancak hiçbir zaman Windows, Mac veya Android'de asla çalışmaz)
  • Ve yukarıda belirtilen diğer tüm sebepler.

5
"GC bir yapıcıyı çalıştırırken sıra dışı bir kilitleme durumunda olabilir" - neden? Tahsis edilen kişi ilk önce tahsis eder, daha sonra yapıcıyı sıradan bir yöntem olarak başlatır. Gerçekten özel bir şey yok, değil mi? Tahsisatçı, yapıcı içindeki yeni tahsislere izin vermek zorundadır (ve bunlar OOM durumlarını tetikleyebilir).
John Dvorak

1
@JanDvorak, Sadece bir kurucudayken "bir yerlerde" bazı özel davranışların olabileceğine işaret etmek istedim, ama haklısın, seçtiğim örnek gerçekçi değildi. Kaldırıldı.
AnoE

Açıklamanızı gerçekten seviyorum ve her iki yanıtı da yanıt olarak işaretlemek isterim (en azından bir artı oyuna sahip). Bu benzetme hoş ve ikincil maliyetlerin düşünce ve açıklama izniniz de öyle ama kod için yardımcı gereklilikler olarak görüyorum (benim iddialarım gibi). Tekniğin bilinen hali, kodu test etmektir, bu nedenle test edilebilirlik, vb. Fiili bir gerekliliktir. Ancak @ JörgWMittag'ın açıklaması fundamental contract of a constructor: to construct somethingaçıktır.
Samuel,

Analoji o kadar iyi değil. Bunu görürsem, matematik yapmak için neden Bash'i kullandığınıza dair bir yorum görmeyi beklerdim ve nedeninize bağlı olarak bu tamamen iyi olabilir. Aynı şey OP için de işe yaramaz, çünkü temelde kurucuyu kullandığınız her yere yorumlarınızı eklemek zorunda kalacaksınız "// Not: bu nesneyi inşa etmek asla geri dönmeyen bir olay döngüsü başlatır".
Brandin

4
“Ne yazık ki, düzenli olarak tüm bunlardan tamamen habersiz programcılar ile tanışacaksınız. Çalışıyor, işte bu kadar.” Çok üzgün ama çok doğru. Profesyonel yaşamımızdaki tek önemli yükün o insanlar olduğunu söylerken hepimiz için konuşabileceğimi düşünüyorum.
Monica ile Hafiflik Yarışları

8

Sorunuzda bu yaklaşımı dışlamak için yeterli nedenler veriyorsunuz ancak buradaki asıl soru "Böyle bir yaklaşımla açıkça daha kötü ya da sapkın bir şey var mı?"

Buradaki ilk düşüncem bunun anlamsız olduğu yönünde. Yapıcınız hiçbir zaman bitmezse, programın başka hiçbir kısmı yapılı nesneye başvuruda bulunamaz, bu nedenle normal bir yöntem yerine yapıcıya koymanın ardındaki mantık nedir. O zaman farkettim ki, tek fark döngü içinde referansların döngüden kısmen oluşturulmuş thiskaçışa izin vermesi olabilirdi . Bu kesin olarak bu durumla sınırlı olmamakla birlikte, thiskaçmaya izin verirseniz , bu referansların her zaman tam olarak yapılandırılmamış bir nesneyi göstereceği garanti edilir .

Bu tür bir durumun etrafındaki anlambilimin C # 'da iyi tanımlanıp tanımlanmadığını bilmiyorum, ancak bunun önemli olmadığını iddia ediyorum çünkü çoğu geliştiricinin içine dalmaya çalışmak isteyeceği bir şey değil.


Yapıcı nesnenin oluşturulduktan sonra çalıştırıldığını ve bu nedenle sızan mantıklı bir dilde tam olarak iyi tanımlanmış bir davranış olmalıdır.
mroman,

@mroman: yapıcıda sızıntı olması uygun olabilir this- ancak yalnızca en fazla türetilmiş sınıfın yapıcıysanız . İki sınıfları varsa Ave Bbirlikte B : Ayapıcısı varsa, Akaçırır ama this, üyeleri Bhala başlatılmamış olacaktır (ve sanal yöntem çağrıları veya silendirler Bkutu wreak tahribat o dayanarak).
Hoffmale

1
C ++ 'da delilik olabilir, evet. Diğer dillerde üyeler gerçek değerlerine atanmadan önce varsayılan bir değer alırlar, bu nedenle gerçekte böyle bir kod yazması aptalca iken davranış hala iyi tanımlanmıştır. Bu üyeleri kullanan yöntemleri çağırırsanız, NullPointerExceptions veya yanlış hesaplamalar (sayılar varsayılan olarak 0 olduğundan) veya bunun gibi şeylerle karşılaşabilirsiniz ancak teknik olarak ne belirleyicidir.
mroman

@mroman CLR için durumun böyle olduğunu gösteren kesin bir referans bulabilir misiniz?
JimmyJames

Peki, kurucu çalıştırılmadan önce üye değerleri atayabilirsiniz, böylece nesne, kurucu çalıştırılmadan önce kendilerine atanmış varsayılan değerler ya da değerler atanmış üyelerle birlikte geçerli bir durumda bulunur. Üyeleri başlatmak için yapıcıya ihtiyacınız yoktur. Bakalım bunu doktorda bir yerde bulabilir miyim?
mroman

1

+1 En azından şaşkınlık için, ama bu yeni cihazlara ifade etmek zor bir kavram. Pragmatik bir düzeyde, nesneler başlatılamıyorsa, yapıcıların durumunu denetlemeniz veya günlüğe kaydetmeniz için var olmayacaksa, inşaatçılar içinde ortaya çıkan istisnaları hata ayıklamak zordur.

Bu tür bir kod deseni yapmanız gerektiğine inanıyorsanız, lütfen bunun yerine sınıfta statik yöntemler kullanın.

Bir nesne sınıf tanımından başlatıldığında başlatma mantığını sağlamak için bir kurucu vardır. Bu nesne örneğini, uygulama mantığınızın geri kalanından çağırmak istediğiniz bir dizi özellik ve işlevsellik içeren bir kapsayıcı olarak yapılandırırsınız.

Eğer "inşa ettiğiniz" nesneyi kullanmayı düşünmüyorsanız, ilk önce nesneyi başlatmanın amacı nedir? Bir kurucuda bir süre (gerçek) bir döngüye sahip olmak, onu asla tamamlamayı düşünmemeniz anlamına gelir ...

C # keşfetmeniz, araçlarınızı bilmeniz ve ne zaman kullanmanız gerektiğine dair birçok farklı yapı ve paradigma içeren çok zengin bir nesne yönelimli dildir:

C # 'da, yapıcılar içinde genişletilmiş veya hiç bitmeyen bir mantık yürütme çünkü ... daha iyi alternatifler var.


1
önceki 9
cevapta

1
Hey, gitmem gerekti :) Bunu yapmanın hiçbir kuralı olmamasına rağmen, daha basit yolların olduğu gerçeğinden dolayı yapmamalısınız. Diğer yanıtların çoğu, neden kötü olduğu konusundaki teknikliğe odaklanıyor, ancak "ancak derler, o yüzden böyle bırakabilir miyim?" Diyen yeni bir deve satış yapmak zor
Chris Schaller

0

Bu konuda doğal olarak kötü bir şey yok. Bununla birlikte, önemli olan bu yapıyı neden kullanmayı seçtiğiniz sorusu. Bunu yaparak ne aldın?

Öncelikle, while(true)bir kurucuda kullanma hakkında konuşmayacağım . Yapıcıda bu sözdizimini kullanmanın kesinlikle yanlış bir tarafı yoktur. Ancak, “kurucuda bir oyun döngüsü başlatmak” kavramı bazı sorunlara neden olabilir.

Bu durumlarda yapıcının basitçe nesneyi yapılandırması ve sonsuz döngüyü çağıran bir işlevi olması çok yaygındır. Bu, kodunuzun kullanıcısına daha fazla esneklik kazandırır. Gösterilebilecek türden sorunlara örnek olarak, nesnenizin kullanımını kısıtlamanız gerekir. Birisi kendi sınıfında "oyun nesnesi" olan bir üye nesnesine sahip olmak isterse, nesneyi inşa etmek zorunda oldukları için tüm oyun döngüsünü yapıcılarında çalıştırmaya hazır olmaları gerekir. Bu, bu konuda çalışmak için işkence edilmiş kodlamaya neden olabilir. Genel durumda, oyun nesnesini önceden tahsis edemez ve daha sonra çalıştırabilirsiniz. Sorun olmayan bazı programlar için, ancak her şeyi önceden tahsis etmek ve ardından oyun döngüsünü çağırmak istediğiniz program sınıfları var.

Programlamada temel kurallardan biri iş için en basit aracı kullanmaktır. Zor ve hızlı bir kural değil, ama çok kullanışlı bir kural. Bir işlev çağrısı yeterliyse, neden bir sınıf nesnesi oluşturmaya uğraşmadınız? Kullanılan daha güçlü ve karmaşık bir araç görürsem, geliştiricinin bunun için bir nedeni olduğunu varsayarım ve ne tür tuhaf numaralar yapabileceğinizi keşfetmeye başlayacağım.

Bunun makul olabileceği durumlar olabilir. Yapıcıda bir oyun döngüsüne sahip olmanızın gerçekten sezgisel olduğu durumlar olabilir. Bu durumlarda, onunla koş! Örneğin, oyun döngünüz, bazı verileri yerleştirmek için sınıflar oluşturan çok daha büyük bir programda gömülü olabilir. Bu verileri üretmek için bir oyun döngüsü çalıştırmanız gerekiyorsa, oyun döngüsünü bir kurucuda çalıştırmak çok uygun olabilir. Sadece bunu özel bir durum olarak kabul edin: bunu yapmak geçerli olacaktır çünkü genel program bir sonraki geliştiricinin ne yaptığınızı ve nedenini anlamalarını sezgisel hale getirir.


Nesnenizi inşa etmek bir oyun döngüsü gerektiriyorsa, en azından bunun yerine fabrika yöntemine koyun. GameTestResults testResults = runGameTests(tests, configuration);yerine GameTestResults testResults = new GameTestResults(tests, configuration);.
user253751

@ immibis Evet, bu mantıksız olduğu durumlar dışında geçerlidir. Nesnenizi sizin için hazırlayan yansıma tabanlı bir sistemle çalışıyorsanız, bir fabrika yöntemi yapma seçeneğiniz olmayabilir.
Cort Ammon

0

Benim izlenimim, bazı kavramların birbirine karıştığı ve çok iyi ifade edilmeyen bir soruyla sonuçlandığı.

Bir sınıfın yapıcı yeni bir başlık açın olabilecek olacak "hiçbir zaman" son (ı kullanmayı tercih olsa while(_KeepRunning)üzerinde while(true)ben yanlış bir yere boolean üyesini ayarlayabilirsiniz çünkü).

Nesne yapısını ve çalışmanın asıl başlangıcını ayırmak için, ipliği oluşturma ve başlatma yöntemini kendi işlevine ayıklayabilirsiniz - Bu yöntemi tercih ederim, çünkü kaynaklara erişim üzerinde daha iyi kontrol sahibi oluyorum.

"Aktif Nesne Kalıbı" na da bakabilirsiniz. Sonunda, sorunun bir "Aktif Nesne" iş parçacığının ne zaman başlayacağını ve benim tercihim yapıyı birleştirmek ve daha iyi kontrol için başlamak olduğunu düşünmemişti.


0

Nesnen bana Görevler başlatmayı hatırlatıyor . Görev nesnesi bir kurucuya sahip, ancak bunu gibi statik fabrika yöntemleri demet de vardır: Task.Run, Task.Startve Task.Factory.StartNew.

Bunlar yapmaya çalıştığınız şeye çok benzer ve muhtemelen bu “kongre” dir. İnsanların sahip olduğu en önemli sorun, bir yapıcının bu kullanımının onları şaşırtmasıdır. @ JörgWMittag, Jörg'ün çok şaşırdığı anlamına gelen temel bir sözleşmeyi ihlal ettiğini söylüyor . Katılıyorum ve buna ekleyecek hiçbir şeyim yok.

Ancak, OP'nin statik fabrika yöntemlerini denediğini öne sürmek istiyorum. Sürpriz ortadan kalkar ve burada tehlikede olan temel bir sözleşme yoktur . İnsanlar özel şeyler yapan statik fabrika yöntemlerine alışkındır ve buna göre adlandırılabilirler.

İnce taneli kontrole izin veren yapıcılar sağlayabilir (@Brandin’in önerdiği gibi var g = new Game {...}; g.MainLoop();, oyuna hemen başlamak istemeyen ve belki de ilk var runningGame = Game.StartNew();adım atmak isteyen kullanıcıları barındıran bir şey gibi) . hemen kolay.


Bu, Jörg'ün temelde şaşırdığı, yani böyle bir sürprinin temelde bir acı olduğu anlamına gelir .
Steve Jessop

0

Bir yapıcıdaki bir süre (gerçek) döngüsü neden gerçekten bozuk?

Bu hiç fena değil.

Neden bir sınıfın kurucusunun hiç bitmeyen bir döngü başlatması (yani oyun döngüsü) için kötü bir tasarım olarak kabul edilir (veya hiç?)?

Bunu neden yapmak istiyorsun? Ciddi anlamda. Bu sınıfın tek bir işlevi var: yapıcı. İstediğiniz tek bir işlevse, yapıcılarımız değil işlevlerimiz var.

Buna karşı bariz nedenlerden bazılarından bahsettiniz, ama en büyüğünü bıraktınız:

Aynı şeyi başarmak için daha iyi ve daha basit seçenekler var.

2 seçenek varsa ve biri daha iyiyse, ne kadar iyi olduğu önemli değil, daha iyisini seçtin. Biz niye arada, işte yok neredeyse GoTo hiç kullanmıyorum.


-1

Bir yapıcının amacı, nesnenizi iyi yapılandırmaktır. Yapıcı tamamlanmadan önce, prensipte o nesnenin herhangi bir yöntemini kullanmak güvensizdir. Elbette, yapıcı içindeki nesnenin yöntemlerini çağırabiliriz, ancak bunu her yaptığımızda, bunun yarı-pişirilmiş haliyle nesne için geçerli olduğundan emin olmak zorundayız.

Tüm mantığı dolaylı olarak kurucuya yerleştirerek, bu türden yükü kendinize eklersiniz. Bu, başlatma ve yeterince iyi kullanım arasındaki farkı tasarlamadığınızı gösterir.


1
bu, önceki 8
cevapta
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.