Java'da hata kodlarını / dizgilerini tanımlamanın en iyi yolu?


118

Java'da bir web hizmeti yazıyorum ve hata kodlarını ve bunlarla ilişkili hata dizelerini tanımlamanın en iyi yolunu bulmaya çalışıyorum . Sayısal bir hata koduna ve birlikte gruplanmış bir hata dizesine ihtiyacım var. Web hizmetine erişen istemciye hem hata kodu hem de hata dizisi gönderilecektir. Örneğin, bir SQLException oluştuğunda, aşağıdakileri yapmak isteyebilirim:

// Example: errorCode = 1, 
//          errorString = "There was a problem accessing the database."
throw new SomeWebServiceException(errorCode, errorString);

İstemci programına şu mesaj gösterilebilir:

"Hata 1 oluştu: Veritabanına erişirken bir sorun oluştu."

İlk düşüncem, Enumhata kodlarından birini kullanmak ve toStringhata dizelerini döndürmek için yöntemleri geçersiz kılmaktı. İşte bulduğum şey:

public enum Errors {
  DATABASE {
    @Override
    public String toString() {
      return "A database error has occured.";
    }
  },

  DUPLICATE_USER {
    @Override
    public String toString() {
      return "This user already exists.";
    }
  },

  // more errors follow
}

Sorum şu: Bunu yapmanın daha iyi bir yolu var mı? Harici bir dosyadan okumak yerine kodda bir çözümü tercih ederim. Bu proje için Javadoc kullanıyorum ve hata kodlarını sıralı olarak belgelemek ve belgelerde otomatik olarak güncellemelerini sağlamak yardımcı olacaktır.


Geç yorum ama bahsetmeye değer bir şey ... 1) Burada gerçekten istisnai durumlarda hata kodlarına ihtiyacınız var mı? Aşağıdaki blabla999 cevabına bakın. 2) Kullanıcıya çok fazla hata bilgisi iletirken dikkatli olmalısınız. Faydalı hata bilgileri sunucu günlüklerine yazılmalı, ancak istemciye minimum düzeyde söylenmelidir (örneğin, "oturum açmada bir sorun vardı"). Bu bir güvenlik meselesidir ve sahtekarların bir yer edinmesini önlemektir.
wmorrison365

Yanıtlar:


161

Pekala, enum çözümünün kesinlikle daha iyi bir uygulaması vardır (ki bu genellikle oldukça iyidir):

public enum Error {
  DATABASE(0, "A database error has occured."),
  DUPLICATE_USER(1, "This user already exists.");

  private final int code;
  private final String description;

  private Error(int code, String description) {
    this.code = code;
    this.description = description;
  }

  public String getDescription() {
     return description;
  }

  public int getCode() {
     return code;
  }

  @Override
  public String toString() {
    return code + ": " + description;
  }
}

Bunun yerine açıklamayı döndürmek için toString () öğesini geçersiz kılmak isteyebilirsiniz - emin değilim. Her neyse, ana nokta, her hata kodu için ayrı ayrı geçersiz kılmanıza gerek olmamasıdır. Ayrıca, sıra değerini kullanmak yerine kodu açıkça belirttiğimi de unutmayın - bu, sırayı değiştirmeyi ve daha sonra hataları eklemeyi / kaldırmayı kolaylaştırır.

Bunun uluslararasılaşmadığını unutmayın - ancak web hizmeti istemciniz size bir yerel tanım göndermediği sürece, yine de bunu kendiniz kolayca uluslararası hale getiremezsiniz. En azından istemci tarafında i18n için kullanacakları hata koduna sahip olacaklar ...


13
Uluslararasılaştırmak için, açıklama alanını bir kaynak paketinde aranabilecek bir dize koduyla değiştirin.
Marcus Downing

@Marcus: Bu fikri beğendim. Bu şeyi kapıdan çıkarmaya odaklanıyorum, ancak uluslararasılaşmaya baktığımızda, önerdiğiniz şeyi yapacağımı düşünüyorum. Teşekkürler!
William Brendel

@marcus, eğer toString () geçersiz kılınmamışsa (ki olması gerekmez), bu durumda dize kodu sadece DATABASE veya bu durumda DUPLICATE_USER olacak olan enum değeri toString () olabilir.
ruble

@Jon Skeet! Bu çözümü beğendim, yerelleştirilmesi (veya başka dillere çevrilmesi vb.) Kolay bir çözümü nasıl üretebilirim? Onu Android'de kullanmayı düşünerek, orada sabit kodlanmış dizeler yerine R.string.IDS_XXXX'i kullanabilir miyim?
AB

1
@AB: Bir kez numaralandırmaya sahip olduğunuzda, ilgili yerelleştirilmiş kaynağı özellik dosyalarıyla veya her neyse, enum değerinden çıkarmak için kolayca bir sınıf yazabilirsiniz.
Jon Skeet

34

Endişelendiğim kadarıyla, hata mesajlarını bir özellikler dosyasında dışsallaştırmayı tercih ediyorum. Bu, uygulamanızın uluslararasılaştırılması durumunda gerçekten yardımcı olacaktır (her dil için bir özellik dosyası). Ayrıca bir hata mesajını değiştirmek daha kolaydır ve Java kaynaklarının yeniden derlenmesine gerek yoktur.

Projelerimde, genellikle bu hatanın özellikler dosyalarındaki anahtarı içeren hata kodlarını (Dize veya tamsayı, pek umursamıyor) içeren bir arayüzüm var:

public interface ErrorCodes {
    String DATABASE_ERROR = "DATABASE_ERROR";
    String DUPLICATE_USER = "DUPLICATE_USER";
    ...
}

özellikler dosyasında:

DATABASE_ERROR=An error occurred in the database.
DUPLICATE_USER=The user already exists.
...

Çözümünüzle ilgili bir başka sorun da bakımdır: sadece 2 hatanız ve zaten 12 satır kodunuz var. Öyleyse, yönetmeniz gereken yüzlerce hata olduğunda Numaralandırma dosyanızı hayal edin!


2
Yapabilseydim bunu 1'den fazla yükseltirdim. Dizelerin kodlanması bakım için çirkin.
Robin

3
String sabitlerini arayüzde saklamak kötü bir fikirdir. Özel yapıcı, paket başına veya ilgili alan ile son sınıflarda numaralandırmaları kullanabilir veya String sabitlerini kullanabilirsiniz. Lütfen John Skeets, Enums ile yanıtlayın. Lütfen kontrol edin. stackoverflow.com/questions/320588/…
Anand Varkey Philips

21

ToString () 'in aşırı yüklenmesi biraz garip görünüyor - bu biraz toString ()' in normal kullanımının bir uzantısı gibi görünüyor.

Ne dersin:

public enum Errors {
  DATABASE(1, "A database error has occured."),
  DUPLICATE_USER(5007, "This user already exists.");
  //... add more cases here ...

  private final int id;
  private final String message;

  Errors(int id, String message) {
     this.id = id;
     this.message = message;
  }

  public int getId() { return id; }
  public String getMessage() { return message; }
}

bana çok daha temiz görünüyor ... ve daha az ayrıntılı.


5
Herhangi bir nesne üzerinde toString () 'in aşırı yüklenmesi (numaralandırmayı bırakın) oldukça normaldir.
cletus

+1 Jon Skeet'in çözümü kadar esnek değil, ancak yine de sorunu güzel bir şekilde çözüyor. Teşekkürler!
William Brendel

2
ToString () 'nin en yaygın ve yararlı bir şekilde nesneyi tanımlamak için yeterli bilgi vermek için kullanıldığını kastetmiştim - genellikle sınıf adını içerir veya nesnenin türünü anlamlı bir şekilde söylemenin bir yolunu içerir. Yalnızca 'Bir veritabanı hatası oluştu' döndüren bir toString () birçok bağlamda şaşırtıcı olacaktır.
Cowan

1
Cowan'a katılıyorum, toString () 'i bu şekilde kullanmak biraz' hack 'görünüyor. Para için hızlı bir patlama ve normal bir kullanım değil. Enum için, toString () enum sabitinin adını döndürmelidir. Bir değişkenin değerini istediğinizde bu bir hata ayıklayıcıda ilginç görünecektir.
Robin

19

Son işimde enum versiyonunda biraz daha derine inmiştim:

public enum Messages {
    @Error
    @Text("You can''t put a {0} in a {1}")
    XYZ00001_CONTAINMENT_NOT_ALLOWED,
    ...
}

@Error, @Info, @Warning, sınıf dosyasında tutulur ve çalışma zamanında kullanılabilir. (Mesaj teslimini açıklamaya yardımcı olacak birkaç başka ek açıklamamız da vardı)

@ Metin, derleme zamanı açıklamadır.

Bunun için aşağıdakileri yapan bir açıklama işlemcisi yazdım:

  • Yinelenen ileti numaralarının olmadığını doğrulayın (ilk alt çizgiden önceki kısım)
  • Sözdizimi-mesaj metnini kontrol edin
  • Enum değeriyle anahtarlanmış metni içeren bir messages.properties dosyası oluşturun.

Hataların günlüğe kaydedilmesine yardımcı olan birkaç yardımcı program yazdım, bunları istisnalar olarak (istenirse) vb.

Açık kaynak kodlu yapmama izin vermelerini sağlamaya çalışıyorum ... - Scott


Hata mesajlarını işlemenin güzel yolu. Zaten açık kaynaklı mıydınız?
bobbel

5

Java.util.ResourceBundle'a bir göz atmanızı tavsiye ederim. I18N'yi önemsemelisiniz, ama ilgilenmeseniz bile buna değer. Mesajları dışa vurmak çok iyi bir fikir. İş adamlarına görmek istedikleri dili tam olarak yazmalarına izin veren bir elektronik tablo verebilmenin yararlı olduğunu gördüm. .Properties dosyalarını derleme zamanında oluşturmak için bir Ant görevi yazdık. I18N'yi önemsiz kılıyor.

Spring'i de kullanıyorsanız, çok daha iyi. MessageSource sınıfları bu tür şeyler için kullanışlıdır.


4

Sadece bu ölü atı kırbaçlamaya devam etmek için , hatalar son müşterilere gösterildiğinde sayısal hata kodlarını iyi bir şekilde kullandık çünkü bunlar gerçek hata mesajını sık sık unuturlar veya yanlış okurlar ancak bazen verebilecek sayısal bir değeri saklayıp rapor edebilirler. gerçekte ne olduğuna dair bir ipucu.


3

Bunu çözmenin birçok yolu var. Tercih ettiğim yaklaşımım arayüzlere sahip olmak:

public interface ICode {
     /*your preferred code type here, can be int or string or whatever*/ id();
}

public interface IMessage {
    ICode code();
}

Artık mesajları sağlayan herhangi bir numaralandırmayı tanımlayabilirsiniz:

public enum DatabaseMessage implements IMessage {
     CONNECTION_FAILURE(DatabaseCode.CONNECTION_FAILURE, ...);
}

Şimdi bunları Dizelere dönüştürmek için birkaç seçeneğiniz var. Dizeleri kodunuzda derleyebilir (ek açıklamaları veya enum yapıcı parametrelerini kullanarak) veya bunları bir yapılandırma / özellik dosyasından veya bir veritabanı tablosundan veya bir karışımdan okuyabilirsiniz. Her zaman çok erken metne dönüşebilir bazı mesajlar gerekecektir, çünkü ikincisi benim tercih yaklaşımdır (yani. Ederken veritabanına bağlanmak veya yapılandırma okuyun).

Her kodun bir yerde kullanıldığından ve yapılandırma dosyalarının tüm beklenen mesajları içerdiğinden emin olmak için arabirimlerimi uygulayan tüm türleri bulmak için birim testleri ve yansıtma çerçeveleri kullanıyorum.

Java'yı https://github.com/javaparser/javaparser veya Eclipse'den gelen gibi ayrıştırabilen çerçeveleri kullanarak , numaralandırmaların nerede kullanıldığını kontrol edebilir ve kullanılmayanları bile bulabilirsiniz.


2

Ben (ve şirketimdeki ekibimizin geri kalanı) hata kodlarını döndürmek yerine istisnalar yaratmayı tercih ediyorum. Hata kodları her yerde kontrol edilmeli, gözden geçirilmeli ve kod miktarı büyüdüğünde kodu okunamaz hale getirme eğilimindedir.

Hata sınıfı daha sonra mesajı tanımlayacaktır.

Not: ve aslında uluslararasılaşmayı da önemsiyoruz!
PPS: ayrıca yükseltme yöntemini yeniden tanımlayabilir ve gerekirse günlük kaydı, filtreleme vb. Ekleyebilirsiniz (en azından İstisna sınıflarının ve arkadaşlarının genişletilebilir / değiştirilebilir olduğu ortamlarda)


üzgünüm Robin, ama sonra (en azından yukarıdaki örnekten), bunlar iki istisna olmalı - "veritabanı hatası" ve "yinelenen kullanıcı" o kadar tamamen farklı ki, ayrı ayrı yakalanabilen iki ayrı hata alt sınıfı oluşturulmalıdır ( biri sistem, diğeri yönetici hatası)
blabla999

ve bir veya diğer istisnayı ayırt etmek için değilse, hata kodları ne için kullanılır? Yani en azından işleyicinin üstünde, tam olarak şudur: etrafından geçen ve eğer-değiştirilen hata kodlarıyla uğraşmak.
blabla999

Bence istisnanın adı, bir hata kodundan çok daha açıklayıcı ve kendini açıklayıcı olacaktır. İyi istisna adlarını keşfetmek için daha fazla düşünmek daha iyidir, IMO.
duffymo

@ blabla999 ah, tam olarak düşüncelerim. Neden genel bir istisnayı yakalayıp sonra "if errorcode == x veya y veya z" testi yapalım. Böyle bir acı ve tahıla aykırı. Ardından, yığınınızdaki farklı düzeylerde farklı istisnaları yakalayamazsınız. Her seviyede yakalamanız ve her bir hata kodunu test etmeniz gerekir. Müşteri kodunu çok daha ayrıntılı hale getiriyor ... Yapabilirsem +1 + daha fazla. Bununla birlikte, sanırım OP'lerin sorusuna cevap vermemiz gerekiyor.
wmorrison365

2
Unutmayın, bu bir web hizmeti içindir. İstemci yalnızca dizeleri ayrıştırabilir. Sunucu tarafında, istemciye son yanıtta kullanılabilecek bir errorCode üyesi olan istisnalar atılır.
pkrish

1

Biraz geciktim ama kendim için güzel bir çözüm arıyordum. Eğer farklı türde bir mesaj hatası yaşıyorsanız, daha sonra isteyeceğiniz daha fazla ayrıntı ve biçimi belirtebilmeniz için basit, özel mesaj fabrikası ekleyebilirsiniz.

public enum Error {
    DATABASE(0, "A database error has occured. "), 
    DUPLICATE_USER(1, "User already exists. ");
    ....
    private String description = "";
    public Error changeDescription(String description) {
        this.description = description;
        return this;
    }
    ....
}

Error genericError = Error.DATABASE;
Error specific = Error.DUPLICATE_USER.changeDescription("(Call Admin)");

DÜZENLEME: tamam, burada enum kullanmak, belirli bir numaralandırmayı kalıcı olarak değiştirdiğiniz için biraz tehlikelidir. Sanırım, sınıfa geçmek ve statik alanlar kullanmak daha iyi olurdu, ancak artık '==' kullanamazsınız. Bu yüzden, ne yapılmaması gerektiğine dair iyi bir örnek olduğunu düşünüyorum (veya bunu yalnızca başlatma sırasında yapın) :)


1
EDIT'inize tamamen katılıyorum, çalışma zamanında bir enum alanını değiştirmek iyi bir uygulama değildir. Bu tasarımla herkes hata mesajını düzenleyebilir. Bu oldukça tehlikelidir. Enum alanları her zaman son olmalıdır.
b3nyc

0

hata kodu / ileti tanımı için enum, i18n endişeleri olsa da yine de güzel bir çözümdür. Aslında iki durumumuz olabilir: kod / mesaj son kullanıcıya veya sistem entegratörüne gösterilir. Daha sonraki durum için I18N gerekli değildir. Bence web hizmetleri büyük olasılıkla sonraki durum.


0

interfaceMesaj sabiti olarak kullanmak genellikle kötü bir fikirdir. Dışa aktarılan API'nin bir parçası olarak istemci programına kalıcı olarak sızacaktır. Kim bilir, sonraki istemci programcıları bu hata mesajlarını (genel) programlarının bir parçası olarak ayrıştırabilir.

Dize biçimindeki değişiklikler istemci programını bozabileceğinden / bozabileceğinden, bunu desteklemek için sonsuza kadar kilitli kalacaksınız.


0

Lütfen aşağıdaki örneği izleyin:

public enum ErrorCodes {
NO_File("No file found. "),
private ErrorCodes(String value) { 
    this.errordesc = value; 
    }
private String errordesc = ""; 
public String errordesc() {
    return errordesc;
}
public void setValue(String errordesc) {
    this.errordesc = errordesc;
}

};

Kodunuzda şöyle deyin:

fileResponse.setErrorCode(ErrorCodes.NO_FILE.errordesc());

0

Yerel hata kodu kaynaklarını yönetmek için bir kurumsal uygulamadaki hata kodlarını tanımlamak için PropertyResourceBundle kullanıyorum. Bu, hata kodlarının sayısı çok büyük ve yapılandırılmışsa, kod yazmak yerine (birkaç hata kodu için geçerli olabilir) hata kodlarını işlemenin en iyi yoludur.

PropertyResourceBundle hakkında daha fazla bilgi için java belgesine bakın

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.