Java'da şablon "metaprogramlama" iyi bir fikir midir?


29

Son derece performansa duyarlı (saniyede milyon kere denir) birkaç işlevi olan oldukça büyük bir projede kaynak bir dosya var. Aslında, önceki bakım görevlisi, koşullamaları tek bir fonksiyonda kontrol etmek için harcanacak zamandan tasarruf etmek için, her biri çok az farklılık gösteren bir fonksiyonun 12 kopyasını yazmaya karar vermiştir.

Ne yazık ki, bu kod korumak için bir PITA olduğu anlamına gelir. Tüm yinelenen kodu kaldırmak ve sadece bir şablon yazmak istiyorum. Ancak, Java dili şablonları desteklemiyor ve jeneriklerin bunun için uygun olduğundan emin değilim.

Mevcut planım, bunun yerine, fonksiyonun 12 kopyasını üreten bir dosya yazmaktır (pratik olarak sadece bir kullanımlık bir şablon genişletici). Elbette, dosyanın programlı olarak neden oluşturulması gerektiğine dair bolca açıklama yapacağım.

Benim endişem, bunun gelecekteki bakıcıların kafa karışıklığına neden olacağı ve belki de dosyayı değiştirdikten sonra yeniden oluşturmayı unutmaları halinde kötü hatalar getirmesi veya programatik olarak oluşturulan dosyayı değiştirmeleri durumunda (hatta daha da kötüsü) yaratacağıdır. Ne yazık ki, C ++ 'da her şeyi yeniden yazmaktan kısaydı, bunu düzeltmenin bir yolunu görmüyorum.

Bu yaklaşımın yararları, dezavantajlardan ağır basıyor mu? Bunun yerine:

  • Performansı etkile ve tek bir bakım işlevini kullan.
  • İşlevin 12 kez neden kopyalanması gerektiğine dair açıklamalar ekleyin ve nezaketle bakım yükünü alın.
  • Jenerikleri şablon olarak kullanmaya çalışın (muhtemelen o şekilde çalışmazlar).
  • Tek bir işleve performansa bağlı kod yapmak için eski bakıcıya bağırma.
  • Performansı ve sürdürülebilirliği korumak için başka bir yöntem?

PS Projenin zayıf tasarımı nedeniyle, işlevin profillenmesi oldukça zordur ... ancak eski bakıcı beni performansın kabul edilemez olduğuna ikna etti. Sanırım bu,% 5'ten fazla anlamına geliyor, ancak bu benim açımdan tam bir tahmin.


Belki biraz daha ayrıntılı çalışmalıyım. 12 kopya da çok benzer bir iş yapıyor ancak dakika farkları var. Farklılıklar fonksiyon boyunca çeşitli yerlerdedir, bu nedenle ne yazık ki çok, çok sayıda koşullu ifade vardır. Etkin bir şekilde 6 "işletme" modu ve 2 "işletme paradigması" vardır (kendim tarafından yazılan kelimeler). İşlevini kullanmak için, işlemin "modu" ve "paradigması" belirtilir. Bu asla dinamik değildir; Her kod parçası, tam olarak bir mod ve paradigma kullanır. Tüm 12 mod-paradigma çiftleri uygulamada bir yerde kullanılır. İşlevler, ikinci paradigmayı temsil eden sayılar ve ilk paradigmayı temsil eden tek sayılarla bile, func1 ila func12 olarak adlandırılır.

Sürdürülebilirlik amaç ise, bunun şimdiye kadarki en kötü tasarımla ilgili olduğunun farkındayım. Ama "yeterince hızlı" gibi görünüyor ve bu kod bir süredir herhangi bir değişikliğe ihtiyaç duymuyor ... Orijinal işlevin silinmediğine dikkat etmem gerekiyor (söyleyebildiğim kadarıyla ölü kod olmasına rağmen) , bu yüzden yeniden yapılanma basit olurdu.


Performans isabeti, fonksiyonun 12 versiyonunu garantilemek için yeterince ciddiyse, buna bağlı kalabilirsiniz. Tek bir işlevi reddettiğinizde (veya jenerik kullanıyorsanız), performans isabetini müşterilerinizi ve işinizi kaybedecek kadar kötü müsünüz?
FrustratedWithFormsDesigner

12
Öyleyse kendi testlerinizi yapmanız gerektiğini düşünüyorum (profillemenin zor olduğunu söylediğinizi biliyorum, ancak sistemin ne kadar büyük bir fark olduğunu görmek için sistemin "kara kutu" performans testlerini hala zor yapabilirsiniz?) olduğunu. Ve eğer farkedilirse, kod üreticisine takılabileceğinizi düşünüyorum.
FrustratedWithFormsDesigner

1
Bu, her bir işlevi farklı bir adla çağırmak yerine, bazı parametrik polimorfizmlerden (veya belki de jeneriklerden) faydalanmış gibi görünebilir.
Robert Harvey,

2
Func1 işlevini adlandırmak ... func12 delilik gibi görünüyor. En azından onları mode1Par2 vs ... veya belki myFuncM3P2 olarak adlandırın.
user949300

2
"programatik olarak oluşturulan dosyayı değiştirirlerse" ... dosyayı yalnızca " Makefile" (veya hangi sistemi kullanırsanız) oluştururken oluşturun ve derleme bittikten hemen sonra kaldırın . Bu şekilde onlar sadece yok yanlış kaynak dosyasını değiştirmek için bir şans var.
Bakuriu

Yanıtlar:


23

Bu çok kötü bir durum, bu en kısa sürede yeniden düzenlemelisiniz - bu en kötü ihtimalle teknik borç - kodun gerçekten ne kadar önemli olduğunu bile bilmiyorsunuz - sadece bunun önemli olduğunu tahmin ediyorsunuz .

Çözümlere gelince ASAP:
Yapılabilecek bir şey özel bir derleme adım ekliyor. Yapması oldukça kolay olan Maven kullanıyorsanız, diğer otomatik yapım sistemlerinin de bununla başa çıkması muhtemeldir. .Ava'dan farklı bir uzantıya sahip bir dosya yazın ve kaynağınızı bu şekilde arayan ve gerçek .java'yı yeniden oluşturan özel bir adım ekleyin. Ayrıca , otomatik olarak oluşturulan dosyaya, değiştirmemek gerektiğini açıklayan büyük bir sorumluluk reddi eklemek de isteyebilirsiniz .

Bir kez oluşturulmuş bir dosyayı kullanarak artıları: Geliştiricileriniz .java çalışmasında değişiklik alamazlar. Eğer taahhütte bulunmadan önce kodu makinelerinde çalıştırırlarsa, değişikliklerinin etkisinin olmadığını göreceklerdir (hah). Ve sonra belki yasal uyarıyı okuyacaklar. Bu özel dosyanın farklı bir şekilde değiştirilmesi gerektiğini hatırlayarak takım arkadaşlarınıza ve gelecekteki kendinize güvenmemek konusunda kesinlikle haklısınız. Ayrıca JUnit, testleri çalıştırmadan önce programınızı derleyeceği için (ve dosyayı da yeniden oluşturur) wel olarak otomatik test yapılmasını sağlar.

DÜZENLE

Yorumlara bakılırsa, cevap sanki bu çalışmayı süresiz olarak yapmanın bir yolu ve projenizin diğer kritik performans bölgelerine konuşlandırmak için uygun olabilir.

Basitçe söylemek gerekirse, öyle değil.

Kendi mini dilinizi yaratmanın, bunun için bir kod oluşturucu yazmanın ve sürdürmenin, gelecekteki bakımcılara öğretmekten bahsetmekten değil, uzun vadede cehennemdir. Yukarıdakiler yalnızca uzun vadeli bir çözüm üzerinde çalışırken sorunu ele almanın daha güvenli bir yolunu sağlar . Bunun alacağı şey benim üstümde.


19
Üzgünüm ama aynı fikirde değilim. OP'nin kodu hakkında bu kararı onun için verebilecek kadar bilginiz yok. Kod üreten bu bana orijinal çözümden daha fazla teknik borç içeriyor gibi geliyor.
Robert Harvey,

6
Robert'ın yorumu abartılamaz. Herhangi bir zamanda "Shark" adı verilen yasadışı bir bahisçi tarafından işletilen çek bozdurma deposuna eşdeğer "teknik borç" eşdeğeri olan bir dosyayı değiştirmemeyi açıklayan bir "sorumluluk reddi" deyin.
corsiKa

8
Elbette otomatik olarak oluşturulan dosya kaynak havuzuna ait değildir (sonuçta kaynak değildir, derlenmiş koddur) ve ant cleaneşdeğeri ne olursa olsun çıkarılmalıdır . Orada bile olmayan bir dosyaya bir feragatname koymaya gerek yok!
Jörg W Mittag

2
@RobertHarvey OP için herhangi bir karar vermiyorum. OP, bu tür şablonlara sahip olmanın sahip olduğu şekilde olmasının iyi bir fikir olup olmadığını sordu ve ben onları korumak için daha iyi bir yol önerdim. Bu ideal bir çözüm değildir ve aslında, ilk cümlede belirttiğim gibi, tüm durum kötüdür ve ilerlemenin çok daha iyi bir yolu problemi ortadan kaldırmak, titrek bir çözüm yapmak değildir. Bu, kodun ne kadar kritik olduğu, performansın ne kadar kötü olduğu ve çok fazla kötü kod yazmadan çalışmanın yolları olup olmadığının doğru bir şekilde değerlendirilmesini içerir.
Ordous

2
@corsiKa Asla bunun kalıcı bir çözüm olduğunu söylemedim. Bu, asıl durum kadar borç olurdu. Yaptığı tek şey sistematik bir çözüm bulunana kadar uçuculuğu azaltmak. Bu, muhtemelen tamamen yeni bir platforma / çerçeveye / dile taşınmayı içerecektir, çünkü Java'da böyle problemlerle karşılaşırsanız - ya çok karmaşık bir şey yapıyorsunuz ya da çok yanlış bir şey yapıyorsunuz.
Ordous

16

Bakım gerçek mi, yoksa sadece sizi rahatsız ediyor mu? Sadece seni rahatsız ediyorsa, rahat bırak.

Performans sorunu gerçek mi, yoksa önceki geliştirici yalnızca bunun olduğunu mu düşünüyor ? Performans problemleri, çoğu zaman olmamak üzere, bir profiler kullanıldığında bile, oldukları yerde değildir. ( Bu tekniği güvenilir bir şekilde bulmak için kullanıyorum.)

Demek ki problemsiz olanlara çirkin bir çözümünüz olabilir, ama gerçek bir problemin de çirkin bir çözümü olabilir. Şüphe duyduğunuzda, yalnız bırakın.


10

Gerçekten, üretim koşulları altında (çöp toplanmasını gerçekçi şekilde tetikleyen gerçekçi bellek tüketimi, vb.) Olduğundan emin olun, tüm bu ayrı yöntemler gerçekten (sadece bir yöntemin aksine) performans farkı yaratır. Bu işlem zamanınızdan birkaç gün alır, ancak bundan sonra çalıştığınız kodu basitleştirerek haftalar kazanabilirsiniz.

Eğer Ayrıca, do tüm fonksiyonları ihtiyaç olduğunu fark, kullanmak isteyebilirsiniz Javassist programlama kodu oluşturmak için. Diğerlerinin de belirttiği gibi Maven ile otomatikleştirebilirsiniz .


2
Ayrıca, performans sorunlarının gerçek olduğu ortaya çıksa bile, bunları incelemeniz, kodunuzla neyin mümkün olduğunu daha iyi anlamanıza yardımcı olacaktır.
Carl Manaster

6

Neden bu sistem için bir çeşit şablon önişlemci \ kod üretimi eklemiyorsunuz? Kodun geri kalanını derlemeden önce ekstra java kaynak dosyalarını yürüten ve yayan özel bir ekstra derleme adımı. Bu, web istemcilerinin wsdl ve xsd dosyalarının dışına çıkarılmasının sıklıkla işe yaradığını göstermektedir.

Elbette bu önişlemci \ kod üretecinin bakımını yapacaksınız, ancak endişelenecek yinelenen çekirdek kod bakımına sahip olmayacaksınız.

Derleme zamanı Java kodu yayınlandığından, ekstra kod için ödenmesi gereken performans cezası yoktur. Ancak yinelenen kodun tümü yerine şablonu kullanarak bakım basitleştirmesi elde edersiniz.

Java jenerikliği, dildeki tip silmesinden dolayı hiçbir performans avantajı sağlamaz, yerine basit döküm kullanılır.

C ++ şablonlarını anladığım kadarıyla, her şablon çağrısı başına birkaç fonksiyonda derlendi. Örneğin, bir std :: vector öğesinde sakladığınız her tür için kopyalanmış derleme ortası zaman kodunu kullanacaksınız.


2

Aklı başında bir Java yöntemi 12 değişkene sahip olacak kadar uzun olamaz ... ve JITC uzun yöntemlerden nefret eder - basitçe bunları uygun şekilde optimize etmeyi reddeder. Bir yöntemi iki kısaca bölerek basit bir ivme faktörü gördüm. Belki de bu böyle gitmektir.

Birden fazla kopyaya sahip OTOH, aynı olsa bile anlamlı olabilir. Her biri farklı yerlerde kullanıldıkça, farklı durumlar için optimize edilirler (JITC onları profillendirir ve nadir durumlarda istisnai bir yola yerleştirir).

İyi bir sebep olduğunu varsayarak kod üretmenin önemli olmadığını söyleyebilirim. Genel gider oldukça düşük ve isimlendirilmiş dosyalar derhal kaynaklarına yönlendiriliyor. Uzun zaman önce kaynak kodunu oluştururken // DO NOT EDIT, her satıra koydum ... Sanırım bu yeterince tasarruf etti.


1

Bahsettiğiniz 'sorun' ile ilgili kesinlikle yanlış bir şey yok. Bildiğim kadarıyla bu iyi bir performans için DB sunucusu tarafından kullanılan tasarım ve yaklaşım tam türüdür.

Her türlü işlem için performansı en üst düzeye çıkarabileceklerinden emin olmak için çok sayıda özel yöntemi vardır: belirli koşullar uygulandığında katılmak, seçmek, birleştirmek vb.

Kısacası, sizin düşündüğünüz gibi kod oluşturma, kötü bir fikirdir. Belki de bir DB'nin sizinkilere benzer bir sorunu nasıl çözdüğünü görmek için bu şemaya bakmalısınız:görüntü tanımını buraya girin


1

Bazı mantıklı soyutlamaları kullanıp kullanamayacağınızı kontrol etmek isteyebilirsiniz ve örneğin ortak işlevsellik için anlaşılabilir bir kod yazmak ve örneğin yöntemler arasındaki farkları "ilkel işlemlere" (kalıp inişine göre) 12'de "ilkel işlemlere" taşımak için örneğin şablon yöntemi desenini kullanmak isteyebilirsiniz. alt sınıfları. Bu, sürdürülebilirliği ve test edilebilirliği geliştirir ve JVM, bir süre sonra ilkel işlemler için çağrılan yöntemin satır içi olmasını sağlayabileceğinden, aslında geçerli kodla aynı performansa sahip olabilir. Tabii ki, bunu bir performans testinde kontrol etmeniz gerekiyor.


0

Özel yöntemlerle ilgili problemi Scala dilini kullanarak çözebilirsiniz.

Scala yöntemleri sıraya sokabilir, bu (kolay yüksek dereceli işlev kullanımıyla birlikte) kod çoğaltmayı ücretsiz olarak önlemeyi mümkün kılar - bu, cevabınızdaki ana soruna benziyor.

Ancak, Scala, derleme sırasında kodla bir çok şeyi güvenli bir şekilde yapmayı mümkün kılan sözdizimsel makrolara sahiptir.

Ve jeneriklerde kullanıldığında, ilkel türlerin sık karşılaşılan boks sorunu Scala'da da çözülebilir: ilkellerin, @specializedek açıklama kullanarak otomatik olarak boks yapmaktan kaçınmaları için genel uzmanlık yapabilir - bu şey dilin kendi içindedir. Bu yüzden, temel olarak, Scala'da bir genel yöntem yazacaksınız ve özel yöntemlerin hızı ile çalışacak. Ayrıca hızlı genel aritmetik gereksiniminiz varsa, farklı sayısal tipler için işlemleri ve değerleri enjekte etmek için Typeclass "pattern" kullanarak kolayca yapılabilir.

Ayrıca, Scala birçok yönden harika. Ve tüm kodları Scala'ya yeniden yazmak zorunda değilsiniz, çünkü Scala <-> Java ile birlikte çalışabilirlik mükemmel. Projeyi oluşturmak için SBT'yi (ölçek oluşturma aracı) kullandığınızdan emin olun .


-1

Söz konusu işlev büyükse, "mode / paradigma" bitlerini bir arabirime dönüştürün ve ardından bu arabirimi işlev olarak bir parametre olarak uygulayan bir nesneyi iletmek işe yarayabilir. GoF buna "Strateji" kalıbı diyor, iirc. İşlev küçükse, artan ek yük önemli olabilir ... biri henüz profilden bahsetti mi? ... daha fazla kişi profillemeden bahsetmelidir.


bu bir cevaptan daha çok bir yorum gibi okur
gnat

-2

Program kaç yaşında? Büyük olasılıkla daha yeni donanım tıkanıklığı giderir ve daha sürdürülebilir bir sürüme geçebilirsiniz. Ancak bakım için bir nedene ihtiyaç vardır, bu nedenle göreviniz kod tabanını iyileştirmek değilse, çalışmakta olduğu gibi bırakın.


1
Bu sadece birkaç saat önce gönderilen bir önceki cevapta yapılan ve açıklanan noktaları tekrar ediyor gibi görünüyor
gnat

1
Diğer bir cevapta belirtildiği gibi bir tıkanıklığın gerçekte nerede olduğunu belirlemenin tek yolu, onu profillendirmektir. Bu kilit bilgi olmadan, performans için önerilen herhangi bir düzeltme saf spekülasyondur.
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.