Bilgilendirici istisnaları ve temiz kodu dengelemenin iyi yolları nelerdir?


11

Herkese açık SDK'mızla, bir istisnanın neden oluştuğu hakkında çok bilgilendirici mesajlar verme eğilimindeyiz. Örneğin:

if (interfaceInstance == null)
{
     string errMsg = string.Format(
          "Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
          ParameterInfo.Name,
          ParameterInfo.ParameterType,
          typeof(IParameter)
    );

    throw new InvalidOperationException(errMsg);
}

Bununla birlikte, kodun ne yaptığından ziyade hata mesajlarına çok fazla odaklanma eğiliminde olduğundan, bu kod akışını karmaşık hale getirir.

Bir meslektaşım, böyle bir şeye fırlatma istisnasının bir kısmını yeniden düzenlemeye başladı:

if (interfaceInstance == null)
    throw EmptyConstructor();

...

private Exception EmptyConstructor()
{
    string errMsg = string.Format(
          "Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
          ParameterInfo.Name,
          ParameterInfo.ParameterType,
          typeof(IParameter)
    );

    return new InvalidOperationException(errMsg);
}

Bu, kod mantığını daha kolay anlaşılır hale getirir, ancak hata işlemeyi yapmak için birçok ek yöntem ekler.

"Uzun istisna iletileri dağınıklık mantığı" sorununu önlemek için başka yollar nelerdir? Öncelikle idiyomatik C # /. NET hakkında soruyorum, ancak diğer dillerin bunu nasıl yönettiğini de yararlı.

[Düzenle]

Her yaklaşımın avantaj ve dezavantajlarına sahip olmak güzel olurdu.


4
IMHO iş arkadaşınızın çözümü çok iyi ve sanırım bu tür çok fazla ekstra yöntem elde ederseniz, en azından bazılarını tekrar kullanabilirsiniz. Programlarınızın anlaşılması kolay yapı taşlarını oluşturdukları sürece, yöntemleriniz iyi adlandırıldığında çok sayıda küçük yönteme sahip olmak iyidir - burada durum böyle görünüyor.
Doc Brown

@DocBrown - Evet, fikri (aşağıda bir cevap olarak, artıları / eksileriyle birlikte ekliyorum) seviyorum, ancak hem akıllı hem de potansiyel sayıda yöntem için, dağınıklık gibi görünmeye başlıyor.
FriendlyGuy

1
Düşünce: Do mesaj yapmaz hepsi istisna ayrıntı taşıyıcıyı. Yakalanan çağrı yığını ile birlikte "seçici" istisna yakalama, arama kodu yakalama ve kendi bağlamını ekleme özelliğini bir arada, hepsi çok daha az ayrıntılı mesajlara izin vermesi gereken bilgilere katkıda bulunur. Son olarak , "istisna yapımı" yönteminize geçmek için ayrıntılar sağlamayı ümit ediyor. Exception.DataSystem.Reflection.MethodBase
radarbob

@ radarbob belki de istisnaların çok ayrıntılı olduğunu mu düşünüyorsun? Belki de kayıt yapmaya çok benziyoruz.
FriendlyGuy

1
@MackleChan, buradaki paradigmanın, 'ne olduğunu zekice, mutlaka dilbilgisi açısından doğru olduğunu ve AI'nin bir iddiasını söylemeye çalışan bir mesaja bilgi koymak olduğunu okudum: ".. boş kurucu aracılığıyla çalıştı, ama .. .." Gerçekten mi? İç adli kodlayıcım bunu yığın-iz-kaybetme yeniden atmalarının ve hatalarının bilinmemesinin ortak hatasının evrimsel bir sonucu olarak görüyor Exception.Data. Vurgu, telemetriyi yakalamak olmalıdır. Burada yeniden düzenleme yapmak iyidir, ancak sorunu özlüyor.
radarbob

Yanıtlar:


10

Neden özel istisna sınıfları yok?

if (interfaceInstance == null)
{
    throw new ThisParticularEmptyConstructorException(<maybe a couple parameters>);
}

Bu, biçimlendirmeyi ve ayrıntıları istisnanın kendisine doğru iter ve ana sınıfı düzensiz bırakır.


1
Artıları: Çok temiz ve düzenli, her istisna için anlamlı isimler sağlar. Eksileri: potansiyel olarak bir sürü ekstra istisna sınıfı.
FriendlyGuy

Eğer önemliyse, sınırlı sayıda istisna sınıfınız olabilir, istisnanın bir parametre olarak kaynaklandığı sınıfı geçirebilir ve daha genel istisna sınıflarının içinde dev bir anahtar kullanabilirsiniz. Ama bunun hafif bir kokusu var - oraya gideceğimden emin değilim.
ptyx

gerçekten kötü kokuyor (sınıflarla sıkıca bağlantılı istisnalar). Özelleştirilebilir istisna mesajlarını istisna sayısına göre dengelemek zor olurdu sanırım.
FriendlyGuy

7

Microsoft (.NET kaynağına bakarak) bazen kaynak / Ortam dizeleri kullanıyor gibi görünüyor. Örneğin ParseDecimal:

throw new OverflowException(Environment.GetResourceString("Overflow_Decimal"));

Artıları:

  • İstisna mesajlarını merkezileştirerek yeniden kullanıma izin verin
  • Kural dışı durum mesajını (muhtemelen kodlaması önemli olmayan) yöntemlerin mantığından uzak tutmak
  • Atılan istisna türü açık
  • Mesajlar yerelleştirilebilir

Eksileri:

  • Bir istisna mesajı değiştirilirse, hepsi değişir
  • Kural dışı durum iletisi, kural dışı durumu atan kod için kolayca kullanılamaz.
  • İleti statiktir ve hangi değerlerin yanlış olduğu hakkında bilgi içermez. Biçimlendirmek istiyorsanız, kodda daha karmaşıktır.

2
Çok büyük bir fayda bıraktınız: İstisna metninin yerelleştirilmesi
17 of 26

@ 17of26 - İyi nokta, ekledi.
FriendlyGuy

Cevabınızı iptal ettim, ancak bu şekilde yapılan hata mesajları "statik" tir; OP'nin kodunda yaptığı gibi bunlara değiştiriciler ekleyemezsiniz. Böylece onun işlevlerinden bazılarını etkin bir şekilde dışladınız.
Robert Harvey

@RobertHarvey - Bir con olarak ekledim. Bu, yerleşik istisnaların neden size asla yerel bilgi vermediğini açıklar. Ayrıca, FYI Ben OP (Bu çözümü biliyordum, ancak başkalarının daha iyi çözümlere sahip olup olmadığını bilmek istedim).
FriendlyGuy

6
@ Bir geliştirici olarak, yerelleştirilmiş istisnalardan tutkuyla nefret ediyorum. Örneğin her seferinde onları yerelleştirmeliyim. çözümler için google.
Konrad Morawski

2

Genel SDK senaryosu için, bunlar bilgilendirici hatalar, statik denetimler sağladığından ve ayrıca XML belgelerine ve Sandcastle tarafından oluşturulan yardım dosyalarına eklemek için belgeler oluşturabileceğiniz için Microsoft Kod Sözleşmelerini kullanmayı kesinlikle öneririm . Visual Studio'nun tüm ücretli sürümlerinde desteklenir.

Ek bir avantaj, müşterileriniz C # kullanıyorsa, kod çalıştırmadan önce bile olası sorunları tespit etmek için kod sözleşmesi referans derlemelerinizden yararlanabilirler.

Kod Sözleşmeleriyle ilgili tüm belgeler burada .


2

Kullandığım teknik , doğrulamayı birleştirmek ve dış kaynak kullanmak ve bir fayda fonksiyonuna tamamen atmaktır.

En önemli tek fayda, iş mantığında tek bir astara indirgenmiş olmasıdır .

Bahse girerim, daha fazla azaltabildiğiniz sürece daha iyisini yapamazsınız - tüm mantıksal onaylamaları ve nesne durumu korumalarını iş mantığından kaldırmak ve yalnızca operasyonel istisnai koşulları korumak.

Elbette, bunu yapmanın yolları vardır - Güçlü yazılan dil, "her zaman geçersiz nesneye izin verilmez" tasarımı, Sözleşmeye Göre Tasarım vb.

Misal:

internal static class ValidationUtil
{
    internal static void ThrowIfRectNullOrInvalid(int imageWidth, int imageHeight, Rect rect)
    {
        if (rect == null)
        {
            throw new ArgumentNullException("rect");
        }
        if (rect.Right > imageWidth || rect.Bottom > imageHeight || MoonPhase.Now == MoonPhase.Invisible)
        {
            throw new ArgumentException(
                message: "This is uselessly informative",
                paramName: "rect");
        }
    }
}

public class Thing
{
    public void DoSomething(Rect rect)
    {
        ValidationUtil.ThrowIfRectNullOrInvalid(_imageWidth, _imageHeight, rect);
        // rest of your code
    }
}

1

[not] Bu konuda yorum olması durumunda bunu sorudan bir cevaba kopyaladım.

Her bir istisnayı, biçimlendirmeyi gerektiren bağımsız değişkenleri alarak sınıfın bir yöntemine taşıyın.

private Exception EmptyConstructor()
{
    string errMsg = string.Format(
          "Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
          ParameterInfo.Name,
          ParameterInfo.ParameterType,
          typeof(IParameter)
    );

    return new InvalidOperationException(errMsg);
}

Tüm istisna yöntemlerini bölgeye dahil edin ve bunları sınıfın sonuna yerleştirin.

Artıları:

  • İletiyi yöntemin temel mantığından uzak tutar
  • Her mesaja mantık bilgisi eklemenizi sağlar (yönteme bağımsız değişkenler iletebilirsiniz)

Eksileri:

  • Yöntem karmaşası. Potansiyel olarak, sadece istisnaları döndüren ve iş mantığıyla gerçekten ilgili olmayan birçok yönteminiz olabilir.
  • Diğer sınıflardaki mesajlar yeniden kullanılamaz

Belirttiğiniz eksilerin avantajlardan çok daha ağır bastığını düşünüyorum.
neontapir

@neontapir eksilerini kolayca adreslenir. Yardımcı yöntemlere IntentionRevealingNames verilmeli ve bazılarına gruplandırılmalıdır #region #endregion(bu da varsayılan olarak IDE'den gizlenmelerine neden olur) ve farklı sınıflar için geçerliyse bunları bir internal static ValidationUtilitysınıfa koyun . Btw, hiçbir zaman bir C # programcısının önünde uzun tanımlayıcı isimlerden şikayet etmeyin.
rwong

Ama bölgeler hakkında şikayet ediyorum. Bana göre, eğer bölgelere başvurmak istediğinizi görürseniz, sınıf muhtemelen çok fazla sorumluluğa sahipti.
neontapir

0

Biraz daha genel hatalardan kurtulabiliyorsanız, sizin için genel bir statik genel döküm işlevi yazabilirsiniz, bu kaynak türünü ihlal eder:

public static I CastOrThrow<I,T>(T t, string source)
{
    if (t is I)
        return (I)t;

    string errMsg = string.Format(
          "Failed to complete {0}, because type: {1} could not be cast to type {2}.",
          source,
          typeof(T),
          typeof(I)
        );

    throw new InvalidOperationException(errMsg);
}


/// and then:

var interfaceInstance = SdkHelper.CastTo<IParameter>(passedObject, "Action constructor");

SdkHelper.RequireNotNull()Sadece girişler üzerindeki gereksinimleri kontrol eden ve başarısız olduklarında atılan olası varyasyonlar (düşünmek ) vardır, ancak bu örnekte alçı sonucu üretmekle birleştirmek kendi kendine belgeleme ve kompakttır.

.Net 4.5 kullanıyorsanız, derleyicinin geçerli yöntem / dosyanın adını yöntem parametresi olarak eklemesini sağlamanın yolları vardır (bkz. CallerMemberAttibute ). Ancak bir SDK için müşterilerinizin 4,5'e geçmesini isteyemezsiniz.


Bu, genel olarak atılan istisnalar (ve bir istisnadaki bilgileri yönetme ile kodu ne kadar karmaşıklaştırdığı vs) hakkındadır, bu özel döküm örneği ile ilgili değildir.
FriendlyGuy

0

İş mantığı hataları için yapmak istediğimiz (gerekli olmayan argüman hataları vb.), Tüm olası hata türlerini tanımlayan tek bir numaraya sahip olmaktır:

/// <summary>
/// This enum is used to identify each business rule uniquely.
/// </summary>
public enum BusinessRuleId {

    /// <summary>
    /// Indicates that a valid body weight value of a patient is missing for dose calculation.
    /// </summary>
    [Display(Name = @"DoseCalculation_PatientBodyWeightMissing")]
    PatientBodyWeightMissingForDoseCalculation = 1,

    /// <summary>
    /// Indicates that a valid body height value of a patient is missing for dose calculation.
    /// </summary>
    [Display(Name = @"DoseCalculation_PatientBodyHeightMissing")]
    PatientBodyHeightMissingForDoseCalculation = 2,

    // ...
}

[Display(Name = "...")]Nitelikler kaynak anahtar hata iletileri çevirmek için kullanılacak dosyaları tanımlar.

Ayrıca, bu dosya, kodunuzda belirli bir hata türünün oluştuğu tüm oluşumları bulmak için bir başlangıç ​​noktası olarak kullanılabilir.

İş Kurallarının kontrolü, ihlal edilen İş Kurallarının listelerini veren uzman Doğrulayıcı sınıflarına devredilebilir.

Ardından, ihlal edilen kuralları taşımak için özel bir İstisna türü kullanırız:

[Serializable]
public class BusinessLogicException : Exception {

    /// <summary>
    /// The Business Rule that was violated.
    /// </summary>
    public BusinessRuleId ViolatedBusinessRule { get; set; }

    /// <summary>
    /// Optional: additional parameters to be used to during generation of the error message.
    /// </summary>
    public string[] MessageParameters { get; set; }

    /// <summary>
    /// This exception indicates that a Business Rule has been violated. 
    /// </summary>
    public BusinessLogicException(BusinessRuleId violatedBusinessRule, params string[] messageParameters) {
        ViolatedBusinessRule = violatedBusinessRule;
        MessageParameters = messageParameters;
    }
}

Arka uç servis çağrıları, ihlal edilen Busines Kuralını kullanıcı tarafından okunabilen bir hata mesajına dönüştüren genel hata işleme koduna sarılır:

public object TryExecuteServiceAction(Action a) {
    try {
        return a();
    }
    catch (BusinessLogicException bex) {
        _logger.Error(GenerateErrorMessage(bex));
    }
}

public string GenerateErrorMessage(BusinessLogicException bex) {
    var translatedError = bex.ViolatedBusinessRule.ToTranslatedString();
    if (bex.MessageParameters != null) {
        translatedError = string.Format(translatedError, bex.MessageParameters);
    }
    return translatedError;
}

İşte ToTranslatedString()için bir uzantısı yöntemidir enumbundan kaynak anahtarlarını okuyabilir [Display]nitelikler ve kullanmak ResourceManagerbu anahtarları çevirmek. İlgili kaynak anahtarının değeri string.Format, sağlanan ile eşleşen yer tutucuları içerebilir MessageParameters. Resx dosyasındaki bir giriş örneği:

<data name="DoseCalculation_PatientBodyWeightMissing" xml:space="preserve">
    <value>The dose can not be calculated because the body weight observation for patient {0} is missing or not up to date.</value>
    <comment>{0} ... Patient name</comment>
</data>

Örnek kullanım:

throw new BusinessLogicException(BusinessRuleId.PatientBodyWeightMissingForDoseCalculation, patient.Name);

Bu yaklaşımla, her yeni hata türü için yeni bir istisna sınıfı sunmaya gerek kalmadan hata mesajının oluşturulmasını hatanın oluşturulmasından ayırabilirsiniz. Görüntülenen mesajın kullanıcı diline ve / veya rolüne vb. Bağlı olması durumunda, farklı kullanıcı arabirimlerinin farklı mesajlar görüntülemesi gerekiyorsa kullanışlıdır.


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.