Bir işlevi çağırırken parametre adlarını bilmemekle başa çıkmak


13

İşte düşüncelerinizi duymak istediğim bir programlama / dil problemi.

Çoğu programcının dil sözdiziminin bir parçası olmayan, ancak kodu daha okunabilir hale getirmeye hizmet ettiği kurallar geliştirdik. Bunlar elbette her zaman bir tartışma konusudur, ancak çoğu programcının uygun bulduğu en azından bazı temel kavramlar vardır. Değişkenlerinizi uygun şekilde adlandırmak, genel olarak adlandırmak, çizgilerinizi aşırı uzun sürmemek, uzun işlevlerden, kapsüllemelerden, bu şeylerden kaçınmak.

Bununla birlikte, henüz yorum yapan birini bulamadığım bir sorun var ve bu sadece en büyük grup olabilir. Bir işlevi çağırdığınızda argümanların anonim olması sorunu.

Fonksiyonlar, f (x) 'in açık bir anlama sahip olduğu matematikten kaynaklanır, çünkü bir fonksiyon genellikle programlamada yaptığı çok daha titiz bir tanıma sahiptir. Matematikteki saf işlevler programlamada olduğundan çok daha az şey yapabilir ve çok daha zarif bir araçtır, genellikle sadece bir argüman alırlar (genellikle bir sayıdır) ve her zaman bir değer (genellikle bir sayı) döndürürler. Bir işlev birden fazla bağımsız değişken alırsa, neredeyse her zaman işlevin etki alanının fazladan boyutlarıdır. Başka bir deyişle, bir argüman diğerlerinden daha önemli değildir. Açıkça sipariş edilirler, elbette, ancak bunun dışında anlamsal bir sıralamaları yoktur.

Bununla birlikte, programlamada daha fazla özgürlük tanımlayan fonksiyonumuz var ve bu durumda bunun iyi bir şey olmadığını iddia ediyorum. Yaygın bir durum, böyle bir işleve sahipsiniz

func DrawRectangleClipped (rectToDraw, fillColor, clippingRect) {}

Tanımlamaya bakıldığında, işlev doğru yazılırsa, ne olduğunu mükemmel bir şekilde açıklar. İşlevi çağırırken, IDE / editörünüzde bir sonraki argümanın ne olması gerektiğini söyleyecek bazı intellisense / kod tamamlama sihri bile olabilir. Fakat bekle. Aslında aramayı yazarken buna ihtiyacım olursa, burada eksik olduğumuz bir şey yok mu? Kodu okuyan kişi bir IDE avantajına sahip değildir ve tanıma geçmedikçe, argüman olarak geçen iki dikdörtgenin hangisinin ne için kullanıldığı hakkında hiçbir fikri yoktur.

Sorun bundan daha da ileri gidiyor. Argümanlarımız bazı yerel değişkenlerden geliyorsa, sadece değişken adını gördüğümüz için ikinci argümanın ne olduğunu bile bilmediğimiz durumlar olabilir. Örneğin bu kod satırını ele alalım

DrawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])

Bu, farklı dillerdeki çeşitli uzantılara hafifletilir, ancak kesinlikle yazılan dillerde bile ve değişkenlerinizi mantıklı bir şekilde adlandırsanız bile, değişkenin işleve geçerken olduğu türden bile bahsetmezsiniz.

Genellikle programlamada olduğu gibi, bu soruna birçok potansiyel çözüm vardır. Birçoğu zaten popüler dillerde uygulanmaktadır. Örneğin C # 'daki adlandırılmış parametreler. Ancak, bildiğim her şeyin önemli dezavantajları var. Her işlev çağrısındaki her parametreyi adlandırmak okunabilir koda yol açamaz. Neredeyse düz metin programlamanın bize sunduğu olasılıkları fazlasıyla büyütüyormuşuz gibi geliyor. Hemen hemen her alanda SADECE metinden taşındık, ancak yine de aynı kodu yazıyoruz. Kodda görüntülenmesi için daha fazla bilgi gerekli mi? Daha fazla metin ekleyin. Her neyse, bu biraz teğetleşiyor, bu yüzden burada duracağım.

İkinci kod parçacığına aldığım bir cevap, muhtemelen ilk olarak bazı adlandırılmış değişkenlere diziyi açacağınız ve daha sonra bunları kullanacağınız, ancak değişkenin adı birçok şey anlamına gelebilir ve çağrıldığı yol size olması gerektiği şekilde söylemeyecektir. çağrılan fonksiyon bağlamında yorumlanabilir. Yerel kapsamda, semantik olarak temsil ettikleri şey olarak leftRectangle ve rightRectangle adlı iki dikdörtgeniniz olabilir, ancak bir işleve verildiğinde temsil ettiklerini genişletmesi gerekmez.

Aslında, değişkenleriniz çağrılan işlev bağlamında adlandırılırsa, bu işlev çağrısıyla potansiyel olarak yapabileceğinizden daha az bilgi girdiğinizden ve bir düzeyde kod daha kötü kodlara yol açıyorsa. RectForClipping içinde sakladığınız bir dikdörtgene neden olan bir yordam ve sonra rectForDrawing sağlayan başka bir yordam varsa, o zaman gerçek DrawRectangleClipped çağrısı sadece tören. Yeni bir şey anlamına gelmeyen bir çizgi var ve orada zaten adınızla açıklamış olsanız bile bilgisayar tam olarak ne istediğinizi biliyor. Bu iyi bir şey değil.

Bu konuda yeni perspektifler duymak isterim. Eminim bu sorunu ilk düşünen ben değilim, o zaman nasıl çözüldü?


2
Kesin sorunun ne olduğu konusunda kafam karıştı ... Burada birkaç fikir var, hangisinin ana noktanız olduğundan emin değilim.
SinirliWithFormsDesigner

1
İşlev belgeleri size bağımsız değişkenlerin ne yaptığını söylemelidir. Sen belgelerine sahip olmayabilir kod okuma o kimse nesne olabilir, ama sonra yok gerçekten kod ne yaptığını biliyor ve ne olursa olsun onlar eğitimli bir tahmin olduğunu okumasını ayıklamak anlamına gelir. Okuyucunun kodun doğru olduğunu bilmesi gereken herhangi bir bağlamda, belgelere ihtiyacı olacaktır.
Doval

3
@Darwin İşlevsel programlamada tüm fonksiyonların hala sadece 1 argümanı vardır. "Birden fazla argüman" iletmeniz gerekiyorsa, parametre genellikle bir demet (sipariş edilmesini istiyorsanız) veya bir kayıttır (olmasını istemiyorsanız). Ayrıca, herhangi bir zamanda işlevlerin özel sürümlerini oluşturmak önemsizdir, böylece gereken bağımsız değişken sayısını azaltabilirsiniz. Hemen hemen her fonksiyonel dil tuples ve kayıtlar için sözdizimi sağladığından, değerleri bir araya getirmek ağrısızdır ve ücretsiz kompozisyon elde edersiniz (tuples tuples alanlarla dönen işlevleri zincirleyebilirsiniz.)
Doval

1
@Bergi İnsanlar saf FP'de çok daha fazla genelleme eğilimindedir, bu yüzden işlevlerin kendilerinin genellikle daha küçük ve daha fazla olduğunu düşünüyorum. Ben olsa çok uzakta olabilir. Haskell ve çete ile gerçek projelerde çalışma konusunda fazla deneyimim yok.
Darwin

4
Bence cevap "Değişkenlerinizin 'deserializedArray' adını vermiyor musunuz?"
whatsisname

Yanıtlar:


10

İşlevlerin sıklıkla kullanılma şeklinin, kod yazmanın ve özellikle de kod okumanın kafa karıştırıcı bir parçası olabileceğini kabul ediyorum.

Bu sorunun cevabı kısmen dile bağlıdır. Bahsettiğiniz gibi, C # parametreleri adlandırdı. Objective-C'nin bu soruna çözümü daha açıklayıcı yöntem adları içerir. Örneğin, stringByReplacingOccurrencesOfString:withString:net parametreleri olan bir yöntemdir.

Groovy'de, bazı işlevler aşağıdaki gibi bir sözdizimine izin veren haritalar alır:

restClient.post(path: 'path/to/somewhere',
            body: requestBody,
            requestContentType: 'application/json')

Genel olarak, bir işleve ilettiğiniz parametre sayısını sınırlayarak bu sorunu çözebilirsiniz. Bence 2-3 iyi bir sınır. Bir işlevin daha fazla parametreye ihtiyacı olduğu anlaşılıyorsa, tasarımı yeniden düşünmeme neden oluyor. Ancak, bunu genel olarak cevaplamak daha zor olabilir. Bazen bir işlevde çok fazla şey yapmaya çalışıyorsunuz. Bazen parametrelerinizi saklamak için bir sınıf düşünmek mantıklıdır. Ayrıca, pratikte, genellikle çok sayıda parametre alan işlevlerin normalde çoğunun isteğe bağlı olarak bulunduğunu görüyorum.

Objective-C gibi bir dilde bile parametre sayısını sınırlamak mantıklıdır. Bunun bir nedeni, birçok parametrenin isteğe bağlı olmasıdır. Bir örnek için, rangeOfString: ve NSString'deki varyasyonlarına bakın .

Java'da sık kullandığım bir desen, akıcı bir stil sınıfını parametre olarak kullanmaktır. Örneğin:

something.draw(new Box().withHeight(5).withWidth(20))

Bu, bir parametreyi bir sınıf olarak kullanır ve akıcı bir stil sınıfıyla kolayca okunabilir bir kod oluşturur.

Yukarıdaki Java snippet'i, parametrelerin sıralamasının çok açık olmayabileceği yerlerde de yardımcı olur. Normalde X'in Y'den önce geldiğini varsayarız. Ve normalde genişlikten önceki yüksekliği bir kural olarak görüyorum, ama bu hala çok net değil ( something.draw(5, 20)).

Ayrıca bazı işlevler de gördüm, drawWithHeightAndWidth(5, 20)ancak bunlar bile çok fazla parametre alamaz veya okunabilirliği kaybetmeye başlarsınız.


2
Java örneğine devam ederseniz, sipariş gerçekten çok zor olabilir. Örneğin awt ile aşağıdaki kurucuları karşılaştırın: Dimension(int width, int height)ve GridLayout(int rows, int cols)(satır sayısı yükseklik, yani GridLayoutönce yükseklik ve Dimensiongenişlik vardır).
Pierre Arlaud

1
Bu tür kıvam uyumsuzlukları da çok PHP (ile eleştirilmektedir eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design Örneğin,): array_filter($input, $callback)karşı array_map($callback, $input), strpos($haystack, $needle)karşıarray_search($needle, $haystack)
Pierre Arlaud

12

Çoğunlukla işlevlerin, parametrelerin ve bağımsız değişkenlerin iyi adlandırılmasıyla çözülür. Ancak bunu zaten keşfettiniz ve bunun eksikliklerini buldunuz. Bu eksikliklerin çoğu, hem çağrı bağlamında hem de çağrılan bağlamda az sayıda parametre ile fonksiyonların küçük tutulmasıyla azaltılır. Özel örneğiniz sorunludur, çünkü aradığınız işlev aynı anda birkaç şey yapmaya çalışır: temel bir dikdörtgen belirtin, kırpma bölgesini belirtin, çizin ve belirli bir renkle doldurun.

Bu, sadece sıfatları kullanarak bir cümle yazmaya çalışmak gibidir. Buraya daha fazla fiil (işlev çağrısı) koyun, cümleniz için bir konu (nesne) oluşturun ve okumak daha kolaydır:

rect.clip(clipRect).fill(color)

Korkunç isimler olsa clipRectve olmasa da color(ve olmamalıdır), yine de türlerini bağlamdan ayırt edebilirsiniz.

Serileştirilmiş örneğiniz sorunludur, çünkü çağıran içerik bir kerede çok fazla şey yapmaya çalışıyor: serileştirmeyi kaldırma ve bir şey çizme. İki sorumluluğu anlamlandıran ve açıkça ayıran isimler atamanız gerekir. En azından böyle bir şey:

(rect, clipRect, color) = deserializeClippedRect()
rect.clip(clipRect).fill(color)

Bir çok okunabilirlik problemi, insanların bağlam ve anlambilimi ayırt etmek için ihtiyaç duyduğu ara aşamaları atlayarak çok özlü olmaya çalışmaktan kaynaklanır.


1
Anlamını açıklığa kavuşturmak için birden fazla işlev çağrısı yapma fikrini seviyorum, ancak bu sadece sorunun etrafında dans etmek değil mi? Temel olarak "Bir cümle yazmak istiyorum, ama dava ettiğim dil bana izin vermiyor, bu yüzden sadece en yakın eşdeğeri kullanabiliyorum"
Darwin

@Darwin IMHO, programlama dilini daha doğal bir dil gibi yaparak iyileştirilebilecek gibi değil. Doğal diller çok belirsizdir ve onları sadece bağlam içinde anlayabiliriz ve aslında asla emin olamayız. Her çağrıda (ideal olarak) dokümantasyon ve mevcut kaynaklar olduğu ve yapıyı netleştiren parantez ve noktalarımız olduğu için işlev çağrıları dizme çok daha iyidir.
maaartinus

3

Uygulamada, daha iyi tasarımla çözüldü. İyi yazılmış işlevlerin 2'den fazla girdi alması son derece nadirdir ve gerçekleştiğinde, bu birçok girdinin bazı uyumlu paketler halinde toplanamaması nadirdir. Bu, işlevlerin parçalanmasını veya parametrelerin toplanmasını oldukça kolaylaştırır, böylece bir işlevi çok fazla yapmazsınız. Birincisi iki girişi vardır, hangi girdinin hangisi olduğu daha kolay anlaşılır hale gelir.

Oyuncak dilimin bununla başa çıkacak ifadeler kavramı vardı ve diğer doğal dil odaklı programlama dillerinin bununla başa çıkmak için başka yaklaşımları vardı, ancak hepsinin başka dezavantajları var. Ayrıca, ifadeler bile işlevlerin daha iyi isimlere sahip olmasını sağlamak için hoş bir sözdiziminden biraz daha fazlasıdır. Bir sürü girdi alırken iyi bir işlev adı oluşturmak her zaman zor olacaktır.


İfadeler gerçekten bir adım gibi görünüyor. Bazı dillerin benzer yetenekleri olduğunu biliyorum ama yaygın olarak FAR. Bahsetmiyorum bile, makroları doğru kullanmayan C (++) saflarından gelen tüm makro nefretle, asla popüler dillerde böyle özelliklere sahip olmayabiliriz.
Darwin

Etki alanına özgü dillerin genel konusuna hoş geldiniz , gerçekten daha fazla insanın avantajını anlamasını diliyorum ... (+1)
Izkata

2

Örneğin, Javascript'te (veya ECMAScript'te ), birçok programcı

parametreleri tek bir anonim nesnede adlandırılmış nesne özellikleri kümesi olarak iletme.

Ve bir programlama uygulaması olarak, programcılardan kütüphanelerine ve oradan hoşuna giden ve kullanan ve daha fazla kütüphane vb.

Misal

Aramak yerine

function drawRectangleClipped (rectToDraw, fillColor, clippingRect)

bunun gibi:

drawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])

, geçerli ve doğru bir stil olan

function drawRectangleClipped (params)

bunun gibi:

drawRectangleClipped({
    rectToDraw: deserializedArray[0], 
    fillColor: deserializedArray[1], 
    clippingRect: deserializedArray[2]
})

, sorunuzla ilgili geçerli ve doğru ve güzel.

Tabii ki, bunun için uygun koşullar olmalı - Javascript'te bu, örneğin C'den çok daha geçerli. Javascript'te, bu, XML için daha hafif bir muadili olarak popüler hale gelen yaygın olarak kullanılan yapısal gösterimi doğurdu. Buna JSON deniyor (daha önce duymuş olabilirsiniz).


Sözdizimini doğrulamak için bu dil hakkında yeterli bilgim yok ama genel olarak bu gönderiyi beğendim. Oldukça zarif görünüyor. +1
IT Alex

Oldukça sık, bu normal argümanlarla birleştirilir, yani 1-3 argüman vardır, bunu takip eder params(genellikle isteğe bağlı argümanlar içerir ve genellikle kendisi isteğe bağlıdır), örneğin bu işlev . Bu, birçok argümana sahip işlevleri kavramayı oldukça kolaylaştırır (benim örneğimde 2 zorunlu ve 6 seçenek argümanı vardır).
maaartinus

0

Obj-C'yi kullanmalısınız, işte burada bir fonksiyon tanımı:

- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject

Ve burada kullanılır:

[someObject performSelector:someSelector withObject:someObject2 withObject:someObject3];

Sanırım yakut benzer yapılara sahiptir ve bunları anahtar-değer listeleriyle diğer dillerde simüle edebilirsiniz.

Java'daki karmaşık işlevler için işlev ifadelerinde kukla değişkenler tanımlamayı seviyorum. Sol-sağ örneğiniz için:

Rectangle referenceRectangle = leftRectangle;
Rectangle targetRectangle = rightRectangle;
doSomeWeirdStuffWithRectangles(referenceRectangle, targetRectangle);

Daha fazla kodlamaya benziyor, ancak solRectangle'ı kullanabilir ve daha sonra kodun gelecekteki ya da olmayabilir olan gelecekteki bir koruyucusu tarafından anlaşılamayacağını düşünüyorsanız, "Yerel değişkeni ayıkla" ile kodu daha sonra yeniden düzenleyebilirsiniz.


Bu Java örneği hakkında, bunun neden iyi bir çözüm olmadığını düşündüğümü yazdım. Bunun hakkında ne düşünüyorsun?
Darwin

0

Benim yaklaşımım, geçici yerel değişkenler oluşturmaktır - sadece onları çağırmakla kalmaz LeftRectangeve RightRectangle. Aksine, daha fazla anlam ifade etmek için biraz daha uzun isimler kullanıyorum. something_rectangleRolleri çok simetrik değilse , isimleri mümkün olduğunca ayırt etmeye çalışırım, örneğin ikisini de çağırmayın .

Örnek (C ++):

auto& connector_source = deserializedArray[0]; 
auto& connector_target = deserializedArray[1]; 
auto& bounding_box = deserializedArray[2]; 
DoWeirdThing(connector_source, connector_target, bounding_box)

ve hatta tek katmanlı bir sarma işlevi veya şablonu yazabilirim:

template <typename T1, typename T2, typename T3>
draw_bounded_connector(
    T1& connector_source, T2& connector_target,const T3& bounding_box) 
{
    DoWeirdThing(connector_source, connector_target, bounding_box)
}

(C ++ bilmiyorsanız ve işareti yoksayın).

İşlev iyi bir açıklama olmadan birkaç garip şey yaparsa - muhtemelen yeniden düzenlenmesi gerekir!

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.