Böyle bir durumda, daima gerekli asgari veriyi bir fonksiyonun içine aktarmanız gerekir.


81

IsAdminBir kullanıcının yönetici olup olmadığını kontrol eden bir fonksiyonum olduğunu varsayalım . Ayrıca yönetici kontrolünün kullanıcı kimliği, adı ve şifresini bir tür kuralla eşleştirerek (önemli değil) yaptığını varsayalım.

Kafamda bunun için iki olası fonksiyon imzası vardır:

public bool IsAdmin(User user);
public bool IsAdmin(int id, string name, string password);

En sık ikinci tür imzaya gidiyorum ve şöyle düşünüyorum:

  • İşlev imzası okuyucuya daha fazla bilgi verir
  • Fonksiyonun içindeki mantık Usersınıf hakkında bilgi sahibi olmak zorunda değildir.
  • Genellikle fonksiyon içinde biraz daha az kodla sonuçlanır.

Ancak bazen bu yaklaşımı sorgularım ve bir noktada bunun hantal olacağının farkındayım. Örneğin, bir işlev on farklı nesne alanı arasında ortaya çıkan bir boole eşleşirse, açıkça tüm nesneyi gönderirdim. Fakat bunun gibi sade bir örnek dışında gerçek nesneyi geçmek için bir neden göremiyorum.

Her iki tarz için herhangi bir tartışmanın yanı sıra önerebileceğiniz genel gözlemleri de takdir ediyorum.

Hem nesne yönelimli hem de işlevsel stiller programlıyorum, bu yüzden soru tüm deyimlerle ilgili olarak görülmelidir.


40
Konu dışı: Merak etmediği halde, neden bir kullanıcının "admin" durumu parolalarına bağlı?
stakx

43
@ syb0rg Yanlış. C # 'da adlandırma kuralı budur .
Magnattic

68
@ syb0rg Doğru, o dili belirtmedi, bu yüzden belirli bir dilin sözleşmesini ihlal ettiğini söylemenin garip olduğunu düşünüyorum.
Magnattic

31
@ syb0rg "İşlev adlarının büyük harfle başlamaması gerektiği" ifadesi yanlıştır, çünkü kullanılması gereken diller vardır. Neyse, seni kırmak istemedim, sadece bunu çözmeye çalışıyordum. Bu, sorunun kendisinden konu dışı olmaya başladı. İhtiyacı göremiyorum, ancak devam etmek istiyorsanız tartışmayı sohbete taşıyalım .
magnattic

6
Genel bir nokta olarak, kendi güvenliğinizi almak genellikle kötü bir şeydir (tm) . Çoğu dilde ve çerçevede bir güvenlik / izin sistemi vardır ve daha basit bir şey olsa bile bunları kullanmak için iyi nedenler vardır.
James Snell

Yanıtlar:


123

Şahsen ben sadece ilk yöntemi tercih ediyorum IsAdmin(User user)

Kullanımı çok daha kolaydır ve IsAdmin kriterleriniz daha sonraki bir tarihte değişirse (belki de rollere veya isActive), yöntem imzanızı her yerde yeniden yazmanıza gerek yoktur.

Ayrıca, bir kullanıcının bir Yönetici olup olmadığını belirleyen hangi özelliklerin reklamını yapmadığınız veya parola özelliğini her yere geçirmediğiniz için muhtemelen daha güvenlidir. Ve syrion iyi bir noktaya sizin ne olur ortaya koyuyor iduymuyor name/ password?

Bir işlevin içindeki kodun uzunluğu, yöntemin işini yapması için gerçekten önemli olmamalıdır ve yardımcı yöntem kodundan çok daha kısa ve basit uygulama koduna sahip olurdum.


3
Bence yeniden düzenleme sorunu çok iyi bir nokta - teşekkürler.
Anders Arpi

1
Yapmazsan, metot imza argümanı yapacaktım. Bu, özellikle, diğer programcılar tarafından sürdürülen yönteminize çok fazla bağımlılık olduğunda faydalı olur. Bu, insanları birbirlerinden uzak tutmaya yardımcı olur. +1
jmort253

1
Diğer bir yaklaşım ( "minimum veri") bayrağı iki durumu bu yanıta katılabilir, ama bana izin olabilir Sen yöntem sonuçlarını önbelleğe almak istiyorum (1): tercih edilebilir. Bir nesneyi önbellek anahtarı olarak kullanmamak ya da bir nesnenin özelliklerini her çağrı sırasında kontrol etmek zorunda olmamak daha iyidir. (2) Yöntemin / işlevin, argümanları serileştirmeyi ve bir tabloya kaydetmeyi içerebilecek şekilde eşzamansız olarak çalıştırılmasını istiyorsunuz. Bekleyen işleri yönetirken bir nesne blobundan bir tam sayı aramak daha kolay ve basittir.
Gladstone

İlkel saplantı karşıtı kalıp, birim testi için korkunç olabilir.
Kasım'ta Samuel

82

İlk imza üstündür çünkü kullanıcı ile ilgili mantığın Usernesnenin içinde kapsanmasına izin verir . Kod tabanınızla ilgili olarak "id, name, password" dizgisinin oluşturulmasında mantıklı olmanız faydalı değildir. Ayrıca, isAdminişlevi karmaşık hale getirir: örneğin biriyle iduyuşmayan birinden geçerse ne olur name? Belirli bir Kullanıcının, şifrelerini bilmemeniz gereken bir bağlamdan yönetici olup olmadığını kontrol etmek istiyorsanız ne olur ?

Ayrıca, üçüncü noktanız üzerinde, ilave argümanlarla birlikte stil lehine bir not olarak; "işlevin içinde daha az kod" olabilir, ancak bu mantık nereye gider? Sadece yok edilemez! Bunun yerine, işlevin çağrıldığı her yerin etrafına yayılır. Beş satırlık kodu tek bir yere kaydetmek için, işlevin kullanımı başına beş satır ödediniz!


45
Mantığını takiben, user.IsAdmin()daha iyi olmaz mıydı?
Anders Arpi

13
Evet kesinlikle.
Asthasr

6
@ AndersHolmström Bu, kullanıcının genel olarak bir yönetici olup olmadığını ya da bulunduğunuz sayfa / form için yalnızca bir yönetici olup olmadığını sınadığınıza bağlıdır. Genel olarak, o zaman evet daha iyi olurdu, ancak yalnızca IsAdminbelirli bir form veya işlev için test yapıyorsanız , kullanıcıyla ilgisi olmamalıdır
Rachel

40
@Anders: hayır olmamalı. Bir kullanıcı bir Yönetici olup olmadığına karar vermemelidir. Ayrıcalıklar veya Güvenlik sistemi gerekir. Bir sınıfa sıkı sıkıya bağlı olan bir şey, onu sınıfın bir parçası
yapmanın

11
@MarjanVenema - Kullanıcı sınıfının bir örneğin bir yönetici olup olmadığına "karar vermemesi" gerektiği fikri biraz kargo yetiştiriciliği olarak beni etkiliyor. Bu genelleştirmeyi gerçekten bu kadar güçlü yapamazsınız. İhtiyaçlarınız karmaşıksa, belki de onları ayrı bir "sistem" içine almak en iyisidir, ancak KISS + YAGNI'ye atıfta bulunmak suretiyle, bu kararı genel bir kural olarak değil, aslında bu karmaşıklıkla karşı karşıya bırakmak istiyorum :-). Ve mantık, Kullanıcının "bir parçası" olmasa bile , Kullanıcı üzerinde daha karmaşık bir sisteme yetki veren bir geçişe sahip olmak için pratiklik meselesi olarak yararlı olabileceğini unutmayın.
Eamon Nerbonne

65

Birincisi, fakat sadece (sadece) başkalarının vermiş olduğu nedenlerden dolayı.

public bool IsAdmin(User user);

Bu güvenli bir tip. Özellikle, Kullanıcı sizin tanımladığınız bir tür olduğundan, argümanları aktarma veya değiştirme konusunda çok az fırsat yoktur. Açıkça Kullanıcılar üzerinde çalışır ve herhangi bir int ve iki dizeyi kabul eden genel bir işlev değildir. Bu, kodunuzun benzendiği Java veya C # gibi güvenli bir dil kullanmanın önemli bir parçasıdır. Belirli bir dil hakkında soru soruyorsanız, sorunuza o dil için bir etiket eklemek isteyebilirsiniz.

public bool IsAdmin(int id, string name, string password);

Burada, herhangi bir int ve iki dize yapacaktır. Adı ve şifreyi aktarmanıza engel olan nedir? İkincisi, kullanmak için daha fazla iş ve hata için daha fazla fırsat sağlar.

Muhtemelen, her iki işlev de, işlev içinde (ilk durumda) veya işlevi çağırmadan önce (ikinci durumda) user.getId (), user.getName () ve user.getPassword () işlevinin çağrılmasını gerektirir. kuplaj miktarı da aynı şekildedir. Aslında, bu işlev yalnızca Kullanıcılar üzerinde geçerli olduğundan, Java'da tüm değişkenleri ortadan kaldırır ve bunu Kullanıcı nesnelerinde örnek bir yöntem haline getiririm:

user.isAdmin();

Bu işlev Kullanıcılar ile sıkı bir şekilde birleştiğinden, onu kullanıcının bir parçası haline getirmek mantıklıdır.

PS Bunun sadece bir örnek olduğuna eminim, fakat bir parola saklıyormuşsunuz gibi görünüyor. Bunun yerine, yalnızca kriptografik olarak güvenli bir parola karma değerini saklamanız gerekir. Oturum açma sırasında, verilen parola aynı şekilde saklanmalı ve saklanan karma ile karşılaştırılmalıdır. Düz metinde parola göndermekten kaçınılmalıdır. Bu kuralı ihlal ederseniz ve ismin (int Str Str) kullanıcı adını kaydederse, o zaman adı ve şifreyi kodunuza aktarırsanız, bunun yerine bir güvenlik sorunu oluşturan şifre kaydedilebilir (günlüklere şifre yazmayın). ) ve bir nesneyi bileşen parçaları yerine geçirmek yerine (ya da bir sınıf yöntemi kullanarak) başka bir argümandır.


11
Bir kullanıcı bir Yönetici olup olmadığına karar vermemelidir. Ayrıcalıklar veya Güvenlik sistemi gerekir. Bir sınıfa sıkıca bağlı olan bir şey, onu sınıfın bir parçası yapmanın iyi bir fikir olduğu anlamına gelmez.
Marjan Venema

6
@MarjanVenema Kullanıcı hiçbir şeye karar vermiyor. Kullanıcı nesnesi / tablosu her kullanıcı hakkında veri depolar. Gerçek kullanıcılar kendileriyle ilgili her şeyi değiştiremezler. Kullanıcı izin verdiği bir şeyi değiştirse bile, e-posta adresini veya kullanıcı kimliğini veya şifresini değiştirirse, bir e-posta gibi güvenlik uyarılarını tetikleyebilir. Kullanıcıların, belirli nesne türleri hakkındaki her şeyi değiştirmeye sihirli bir şekilde izin verdiği bir sistem mi kullanıyorsunuz? Böyle bir sisteme aşina değilim.
GlenPeterson

2
@GlenPeterson: Beni kasıtlı olarak yanlış anlıyor musunuz? Kullanıcı derken elbette kullanıcı sınıfını kastediyordum.
Marjan Venema

2
@GlenPeterson Tamamen kapalı konu, ancak çoklu karma ve şifreleme fonksiyonları turu verileri zayıflatacak.
Gusdor

3
@MarjanVenema: Kasıtlı olarak zor değilim. Bence çok farklı bakış açılarımız var ve sizinkini daha iyi anlamak istiyorum. Bunun daha iyi tartışılabileceği yeni bir soru açtım: programmers.stackexchange.com/questions/216443/…
GlenPeterson 4:13

6

Seçtiğiniz dil, yapıları değerine göre geçmiyorsa, (User user)değişken daha iyi görünür. Ya bir gün kullanıcı adını not almaya karar verirsen ve kullanıcıyı tanımlamak için sadece ID / şifreyi kullanırsan?

Ayrıca, bu yaklaşım kaçınılmaz olarak uzun ve abartılı yöntem çağrılarına veya tutarsızlıklara yol açmaktadır. 3 argüman geçiyor mu? 5 nasıl? Veya 6? Neden bir nesneyi geçmek, kaynaklar açısından ucuzsa (belki 3 argümanı geçmekten daha ucuz), neden bunu düşünüyorsunuz?

Ve ben (tür) ikinci yaklaşımın okuyucuya daha fazla bilgi sağladığını kabul etmiyorum - sezgisel olarak, yöntem çağrısı "verilen kimlik / ad / şifre kombinasyonunun yönetici haklarına sahip olup olmadığını" değil, "kullanıcının yönetici haklarına sahip olup olmadığını" soruyor.

Bu nedenle, Usernesneyi geçmek büyük bir yapının kopyalanmasına neden olmazsa , ilk yaklaşım benim için daha temiz ve daha mantıklı olur.


3

Muhtemelen ikisine de sahip olurdum. Dış API olarak birincisi, zaman içinde istikrarlılık umuduyla. İkincisi, harici API tarafından çağrılan özel bir uygulama olarak.

Daha sonraki bir zamanda kontrol kuralını değiştirmek zorunda kalırsam, harici adında yeni bir imzayla yeni bir özel işlev yazardım.

Bu, iç uygulamayı değiştirmek için kolaylık avantajına sahiptir. Ayrıca, bir anda aynı anda mevcut olan her iki işleve ihtiyaç duymanız ve bazı harici bağlam değişikliklerine bağlı olarak birini veya diğerini çağırmanız gerçekten normaldir (örneğin, eski Kullanıcıları ve farklı alanlara sahip yeni Kullanıcıları bir arada bulundurmuş olabilirsiniz).

Sorunun başlığı ile ilgili olarak, her iki durumda da asgari gerekli bilgileri veriyorsunuz. Kullanıcı bilgileri içeren en küçük Uygulama İlişkili Veri Yapısı gibi görünmektedir. Üç alan kimliği, parola ve ad, gerçekte ihtiyaç duyulan en küçük uygulama verileri gibi görünüyor, ancak bunlar gerçekten Uygulama düzeyinde bir nesne değil.

Başka bir deyişle, bir veritabanı ile uğraşıyorsanız, bir Kullanıcı bir kayıt olurken, ID, şifre ve giriş bu kayıttaki alanlardır.


Özel yöntemin nedenini göremiyorum. Neden her ikisini de koruyorsun? Özel yöntemin farklı argümanlara ihtiyacı varsa, yine de herkese açık olanı değiştirmeniz gerekecektir. Ve bunu halka açık olanı kullanarak test edeceksiniz. Ve hayır, ikisine de daha sonra ihtiyaç duymanız normal değildir. Burada tahmin ediyorsun - YAGNI.
Piotr Perak

Örneğin, Kullanıcı veri yapısının baştan başka bir alanı olmadığını varsayıyorsunuz. Bu genellikle doğru değildir. İkinci fonksiyonlar kullanıcının hangi iç kısmının yönetici olup olmadığını görmek için kontrol edilmesini sağlar. Tipik kullanım durumları şu şekilde olabilir: eğer kullanıcı oluşturma süresi bir tarihten önce ise, eski kuralı çağırın, eğer yeni kuralı çağırmazsanız (kullanıcının diğer bölümlerine bağlı olabilir veya aynı fakat başka bir kuralı kullanır). Kodu bölerek böylelikle iki minimal fonksiyon elde edersiniz: minimal kavramsal API ve minimum uygulama fonksiyonu.
kriss

Diğer bir deyişle, bu şekilde, Kullanıcının hangi bölümlerinin kullanıldığı veya kullanılmadığı iç fonksiyon imzasından hemen görülebilir. Üretim kodunda bu her zaman açık değildir. Halen birkaç yıllık bakım geçmişine sahip büyük bir kod tabanı üzerinde çalışıyorum ve bu tür bir bölme uzun süreli bakım için çok kullanışlıdır. Kodu korumayı düşünmüyorsanız, gerçekten işe yaramaz (ancak kararlı bir kavramsal API sağlamak da muhtemelen işe yaramaz).
kriss

Başka bir kelime: Kesinlikle iç yöntemi dış yöntemle test etmeyeceğim. Bu kötü test uygulaması.
kriss

1
Bu iyi bir pratik ve bunun için bir isim var. Buna "tek sorumluluk ilkesi" denir. Hem sınıflara hem de yöntemlere uygulanabilir. Tek bir sorumluluğu olan yöntemlere sahip olmak, daha modüler ve sürdürülebilir kod yapmak için iyi bir yoldur. Bu durumda, bir görev kullanıcı nesnesinden temel veri kümesini seçmek ve diğer bir görev, kullanıcının yönetici olup olmadığını görmek için bu temel veri kümesini denetlemektir. Bu prensibi izleyerek, metot oluşturma ve kod çoğaltmasından kaçınma bonusunu elde edersiniz. Bu aynı zamanda, kriss tarafından belirtildiği gibi daha iyi birim testleri yaptığınız anlamına gelir.
diegoreymendez

3

Sınıfları (başvuru türleri) yöntemlere göndermek için bir olumsuz nokta var ve bu da Toplu Atama Güvenlik Açığı . Bir IsAdmin(User user)yöntem yerine yöntemim olduğunu hayal edin UpdateUser(User user).

Eğer Usersınıf denilen bir Boole özelliği vardır IsAdmin, ve benim yöntemin yürütülmesi sırasında kontrol yoksa, o zaman kitle atama savunmasız değilim. GitHub 2012'de bu imzayla saldırıya uğradı.


2

Bu yaklaşımla giderdim:

public bool IsAdmin(this IUser user) { /* ... */ }

public class User: IUser { /* ... */ }

public interface IUser
{
    string Username {get;}
    string Password {get;}
    IID Id {get;}
}
  • Arabirim türünü bu işlev için bir parametre olarak kullanmak, Test için sahte Kullanıcı nesnelerini tanımlamak için Kullanıcı nesnelerine geçmenizi ve bu fonksiyonun hem kullanımı hem de bakımı konusunda size daha fazla esneklik sağlar.

  • Ayrıca this, işlevi tüm IUser sınıflarının bir uzantısı yöntemi yapmak için anahtar kelimeyi de ekledim ; Bu şekilde daha OO dostu bir şekilde kod yazabilir ve bu işlevi Linq sorgularında kullanabilirsiniz. Daha basit olanı, bu işlevi IUser ve User'da tanımlamak olacaktır ancak bu yöntemi bu sınıfın dışına koymaya karar vermenizin bir nedeni olduğunu varsayıyorum?

  • Kimliğinizin özelliklerini yeniden tanımlamanıza izin verdiği için özel arayüzü IIDkullanıyorum int; örneğin Hiç değiştirmek gerektiğinde intüzere long, Guidbaşka, ya da bir şey. Muhtemelen fazla abartılıdır, ancak erken kararlara kilitlenmemeniz için işleri esnek hale getirmeye çalışmak her zaman iyidir.

  • Not: IUser nesneniz, Kullanıcı sınıfına göre farklı bir ad alanında ve / veya montajında ​​olabilir; bu nedenle, Kullanıcı kodunuzu IsAdmin kodunuzdan tamamen ayrı tutma seçeneğine sahipsiniz, yalnızca bir başvurulan kütüphaneyi paylaşıyorsunuz. Tam olarak ne yaptığınızı, bu eşyaların nasıl kullanıldığından şüphelendiğinizi gösteren bir çağrı var.


3
Kullanıcı burada faydasız ve sadece karmaşıklık ekler. Testlerde neden gerçek Kullanıcı kullanamadığını anlamıyorum.
Piotr Perak

1
Çok az bir çaba için gelecekteki esnekliği arttırır, bu nedenle şu anda değer katmamakla birlikte zarar vermez. Şahsen ben her zaman bunu yapmayı tercih ederim, çünkü tüm senaryolar için gelecekteki olasılıkları düşünmek zorunda kalmaktan kaçınıyor (bu şartların öngörülemeyen niteliği göz önüne alındığında neredeyse imkansız, ve kısmen de olsa iyi cevap alabilmek için çok daha fazla zaman alacaktır) bir arayüze yapıştırın).
JohnLBevan

@Peri gerçek bir Kullanıcı sınıfı oluşturmak karmaşık olabilir, arayüz almak alay etmenizi sağlar, böylece testlerinizi basit tutabilirsiniz
jk.

@JohnLBevan: YAGNI !!!
Piotr Perak

(jk .: Kullanıcı oluşturmak zorsa o zaman bir şeyler yanlış olur
Piotr Perak

1

Feragatler:

Cevabım için, stil meselesine odaklanacağım ve bir kimlik, giriş ve düz parolanın güvenlik açısından iyi bir parametre seti olup olmadığını unutacağım. Kullanıcının ayrıcalıklarını bulmak için kullandığınız temel veri grubuna cevabımı tahmin edebilmelisiniz ...

Ayrıca user.isAdmin()eşdeğer olmayı düşünüyorum IsAdmin(User user). Bu senin için bir seçimdir.

Cevap:

Tavsiyem yalnızca iki kullanıcı için:

public bool IsAdmin(User user);

Veya her ikisini de, çizgileri boyunca kullanın:

public bool IsAdmin(User user); // calls the private method below
private bool IsAdmin(int id, string name, string password);

Genel yöntemin nedenleri:

Genel yöntemleri yazarken, bunların nasıl kullanılacağı hakkında düşünmek genellikle iyi bir fikirdir.

Bu özel durumda, genellikle bu yöntemi çağırmanızın nedeni, belirli bir kullanıcının yönetici olup olmadığını anlamaktır. İhtiyacın olan bir yöntem. Gerçekten, her arayanın göndermesi için doğru parametreleri seçmesini istemiyorsunuz (ve kod çoğaltması nedeniyle hataları riske atıyorsunuz).

Özel yöntemin sebepleri:

Sadece minimum gerekli parametre setini alan özel yöntem bazen iyi bir şey olabilir. Bazen çok değil.

Bu yöntemleri bölmenin iyi yanlarından biri, özel sürümü aynı sınıftaki birkaç ortak yöntemden arayabilmenizdir. Örneğin, (ne nedenle olursa olsun) biraz farklı mantığa sahip olmak Localuserve RemoteUser(belki de uzak yöneticileri açıp kapamak için bir ayar olabilir mi?):

public bool IsAdmin(Localuser user); // Just call private method
public bool IsAdmin(RemoteUser user); // Check if setting is on, then call private method
private bool IsAdmin(int id, string name, string password);

Ayrıca, herhangi bir nedenden dolayı özel yöntemi herkese açık hale getirmeniz gerekiyorsa ... yapabilirsiniz. Sadece değiştirmek kadar kolay privateiçin public. Bu durum için büyük bir avantaj gibi görünmüyor olabilir, ama bazen gerçekten öyle.

Ayrıca, birim testi yaparken çok daha fazla atom testi yapabilirsiniz. Bu yöntem üzerinde testler yapmak için tam bir kullanıcı nesnesi oluşturmak zorunda kalmamak çok hoş bir şey. Tam kapsama istiyorsanız, her iki aramayı da test edebilirsiniz.


0
public bool IsAdmin(int id, string name, string password);

Listelenen nedenlerden dolayı kötü. Bu anlama gelmez

public bool IsAdmin(User user);

otomatik olarak iyidir. : Özellikle Kullanıcı benzeri yöntemler sahip bir nesne ise getOrders(), getFriends(), getAdresses(), ...

Etki alanını yalnızca kimlik, ad, parola içeren bir UserCredentials türüyle yeniden yapılandırmayı ve gereksiz tüm kullanıcı verileri yerine bunu IsAdmin'e geçirmeyi düşünebilirsiniz.

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.