Makale Özeti

Exception'lar tüm yazılımcıların korkulu rüyasıdır. Özellikle de uygulama geliştirme aşamasında öngörülememiş bir durumsa. Hiçbir yazılımcının müşterisine teslim ettiği bir ürününde hata görmek isteyeceğini sanmıyorum. Peki çözüm nedir? Tüm exception'ları yakalamanın, kullanıcıyı uygun bir ekranla uyarmanın ve hataların sistem hata kayıtlarına eklenmesini sağlamanın yazılan tüm projeyi otomatik olarak kapsayabileceği bir yöntemi yok mudur? Vardır.

Makale

Exception'lar tüm yazılımcıların korkulu rüyasıdır. Özellikle de uygulama geliştirme aşamasında öngörülememiş bir durumsa. Hiçbir yazılımcının müşterisine teslim ettiği bir ürününde hata görmek isteyeceğini sanmıyorum. Peki çözüm nedir? Tüm exception'ları yakalamanın, kullanıcıyı uygun bir ekranla uyarmanın ve hataların sistem hata kayıtlarına eklenmesini sağlamanın yazılan tüm projeyi otomatik olarak kapsayabileceği bir yöntemi yok mudur? Vardır.

Belirtmek istediğim ve örneğimizde de göreceğimiz özel nokta şudur ki, bahsettiğim durum tüm işlemlerimizi "try...catch" blokları arasında yazmak ve ya tüm formlarımızı "try...catch" bloklarına boğmak değildir. Bahsettiğim şeyi teorik olarak tek bir cümleyle açıklamak gerekse şu cümleyi kullanırdım: "Tüm uygulamamızı tek bir try...catch bloğu arasında yazmak.".


Peki nasıl yapacağız? "AppDomain" nesnemizin "UnhandledException" ve "Application" nesnemizin "ThreadException" özelliklerini kullanarak, çok basit bir şekilde.


Sözü fazla uzatmadan örneğimize başlamak istiyorum (örnek C# ile geliştirilmiştir). Öncelikli olarak bir üst paragrafta bahsettiğim 2 özelliğimizi tanıyalım.


İlk özelliğimiz "AppDomain" üzerinde bulunan "UnhandledException" özelliği. Aslında özellik kelimesinden ziyade "EventHandler" kelimesini kullanmam gerekiyor çünkü "UnhandledException"'a bir methodu hook (çengellemek) edeceğiz ve sistemin hata üretmesi durumunda bu methodun çalışmasını sağlayacağız. Kısaca açıklamak gerekirse; "UnhandledException" EventHandler'ı herhangi bir try...catch bloğu tarafından yakalanmamış ve herhangi bir EventHandler tarafından handle edilmemiş exception'ları yakalar ve hook edilmiş methodu çalıştırır. Teknik bilgiyi MSDN üzerinden alabilirsiniz.


Diğer özelliğimiz ise "Application" üzerinde bulunan "ThreadException" özelliğidir. Tabi siz "özellik" dediğime bakmayın, aslında bu da bir "EventHandler", benimkisi sadece bir alışkanlık. Bu EventHandler'ın görevi de Thread üzerinde oluşan ve yakalanmamış hataları yakalamaktır. Teknik bilgiyi MSDN üzerinden alabilirsiniz.


Biz bu EventHandler'ları nerede tanımlayacağız soranlar olabilir, cevap çok basit, "Main" methodunuz içerisinde. Bildiğiniz üzere "Main" methodu uygulamanızın giriş noktasıdır. Visual Studio 2003 kullanıcıları "Main" methodunu "Form1.cs" dosyasında görürken, Visual Studio 2005 bu methodu "Program.cs" isminde bir dosya oluşturarak bu dosyanın içerisine atıyor ve "Form1.cs" buradan çalıştırılıyor. Visual Studio 2005 tarafından oluşturulmuş olan "Program.cs" dosyası tam olarak aşağıdaki gibidir (benim oluşturduğum projenin ismi UnhandledExceptionManagement'tır):


using System;
using System.Windows.Forms;

namespace UnhandledExceptionManagement
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles ();
            Application.Run ( new Form1 () );
        }
    }
}


Hata oluşması durumunda ekrana gelecek olan formumuzu aşağıdaki şekilde hazırlayalım ve formumuzu "frmException.cs" ismiyle kaydedelim. Tabi formumuzun class'ı da "frmException" olacak.


Oluşturduğumuz formun kaynak kodunu aşağıdaki şekilde yazalım:


using System;
using System.Windows.Forms;

namespace UnhandledExceptionManagement
{
    partial class frmException : Form
    {
        #region Fields
        string m_ExceptionName;
        string m_ExceptionMessage;
        object m_ExceptionSource;
        #endregion

        #region Properties
        public string ExceptionName
        {
            set { m_ExceptionName = value; }
        }
        public string ExceptionMessage
        {
            set { m_ExceptionMessage = value; }
        }
        public object ExceptionSource
        {
            set { m_ExceptionSource = value; }
        }
        #endregion

        public frmException ()
        {
            InitializeComponent();
        }

        private void frmException_Load( object sender , EventArgs e )
        {
            lbExceptionName.Text = m_ExceptionName;
            txDetails.Text = m_ExceptionMessage;
        }
    }
}





Şimdi birlikte methodlarımızı yazmaya başlayalım:


Öncelikle projemize "Class" template'ini seçerek "MyExceptionHandler.cs" isminde bir dosya ekleyelim. Sonrasında Visual Studio'nun bizim için otomatik olarak oluşturduğu class'ı aşağıdaki namespace'leri kullanmasını söyleyelim:


using System;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;


Class'ımıza boş bir constructor ekleyelim:


public MyExceptionHandler()
{

}


Class'ımıza aşağıdaki 2 methodu ekleyelim:


public void OnUnhandledException( object sender , UnhandledExceptionEventArgs e )
{
    HandleUnhandledException ( sender , e.ExceptionObject );
}

public void OnThreadException( object sender , ThreadExceptionEventArgs e )
{
    HandleUnhandledException ( sender , e.Exception );
}


Oluşturmuş olduğumuz 2 method içerisinde de kullandığımız "HandleUnhandledException" methodumuzu oluşturalım (method'da yapılan işlemlerin açıklamalarını kodun içerisine yazdım, ayrıca anlatmayacağım):


private void HandleUnhandledException( object sender , object exception )
{
    Exception m_Exception;
    frmException m_Form;
    string m_Message;

    try
    {
        // object tipindeki exception'ımızı "Exception" tipine çevirelim.
        m_Exception = ( Exception )exception;

        // Yeni bir "frmException" oluşturalım.
        m_Form = new frmException ();

        // Oluşturduğumuz m_Form'un özelliklerini atayalım.
        m_Message = "Type: \n\t" + m_Exception.GetType ().ToString () + "\n" +
                                "Source: \n\t" + m_Exception.Source + "\n" +
                                "Message: \n\t" + m_Exception.Message + "\n" +
                                "Trace: \n\t" + m_Exception.StackTrace;

        m_Form.ExceptionName = m_Exception.GetType ().ToString ();
        m_Form.ExceptionMessage = m_Message;
        m_Form.ExceptionSource = exception;

        // Oluşturduğumuz m_Form'u gösterelim.
        m_Form.ShowDialog ();

        try
        {
            // EventLog kaynağımız yoksa EventLog kaynağımızı oluşturalım.
            if ( !EventLog.SourceExists ( "UnhandledExceptionManagement" ) )
                EventLog.CreateEventSource ( "UnhandledExceptionManagement" , "UnhandledExceptionManagement" );

            // Yeni bir EventLog oluşturalım.
            EventLog m_EventLog = new EventLog ();

            // EventLog'umuzun kaynağını belirtelim.
            m_EventLog.Source = "UnhandledExceptionManagement";

            // EventLog'umuzu dolduğu zaman en eski log bilgisini silecek şekilde ayarlayalım.
            m_EventLog.ModifyOverflowPolicy ( OverflowAction.OverwriteAsNeeded , m_EventLog.MinimumRetentionDays );

            // Hatayı EventLog'umuza yazalım.
            EventLog.WriteEntry ( "UnhandledExceptionManagement" , m_Message );

            // EventLog'u kapatalım.
            m_EventLog.Close ();
        }
        catch ( Exception )
        {
            // EventLog'a yazılması sırasında bir hata aldıysak herhangi birşey yapmayacağız.
        }
    }
    catch ( Exception )
    {
        // Exception ekranının gösterilmesi sırasında da bir hata aldıysak "Tanımlanamayan hata" mesajı ile uygulamamızı sonlandıralım

        try
        {
            MessageBox.Show ( "Tanımlanamayan hata" , "Hata" , MessageBoxButtons.OK , MessageBoxIcon.Stop );
        }
        finally
        {
            Environment.Exit ( 1 );
        }
    }
}


Şimdi EventHandler'ımıza geri dönelim ve method'larımızı tanıtarak kodu aşağıdaki şekilde değiştirelim:


using System;
using System.Windows.Forms;

namespace UnhandledExceptionManagement
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles ();

            MyExceptionHandler m_Handler = new MyExceptionHandler ();
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler ( m_Handler.OnUnhandledException );
            Application.ThreadException += new System.Threading.ThreadExceptionEventHandler ( m_Handler.OnThreadException );

            Application.Run ( new Form1 () );
        }
    }
}


Artık programımızı çalıştırabiliriz. F5 tuşuna basalım ve Form1'in çalışan halini görelim (Form1 formu projeyi oluşturduğunuz anda eklenen formdur, test için bu formu kullanacağız.):





Şimdi de Form1 üzerine yeni bir buton ekleyerek butonun OnClick event'ine aşağıdaki kodu yazalım:


private void button1_Click( object sender , EventArgs e )
{
    object m_Object = null;
    MessageBox.Show ( m_Object.ToString () );
}


Bildiğiniz üzere "null" bir objenin string'e çevirilmek istenmesi durumunda "System.NullReferenceException" alınır. Programımızı tekrar çalıştıralım:





Butonumuza tıklayalım:





Exception ekranımızdaki "bnClose" butonuna tıklayalım:





Gördüğünüz gibi programımız hala çalışmakta. Normal olarak hatalı satırları try...catch blokları arasına yazmadığımızdan dolayı programımızın kapanması ve ya Visual Studio ekranına düşmesi gerekirdi.


Şimdi de " Başlat / Denetim Masası / Yönetimsel Araçlar " menülerini izleyerek Event Viewer'ımıza bakalım:





Artık "UnhandledExceptionManagement" tipinde bir hata kategorisine sahibiz. Şimdi de hatalarımıza bakalım:





Ve hatanın üzerine tıkladığımızda da:





Görmüş olduğunuz gibi hata yakalamayı bu şekilde son derece kolay hale getirebiliyoruz. Hatayı yakaladıktan sonra mail gönderilmesini sağlamak gibi birçok olayı da hatanın ekranda gösterildiği noktada yapmanız mümkün.


Yazmış olduğumuz uygulamanın kaynak kodlarını "Download" bölümünden indirebilirsiniz. Proje Visual Studio 2005 Beta 2 ile üretilmiştir.


Exception'sız günler dileği ile.



Coşkun SUNALI
http://sunali.com
Örnek uygulama. Visual Studio 2005 Beta 2 ile yazılmıştır.