Hata Yakalama(Error Handling)

Derler ki Wright kardeşler uçağı uçurmadan önce düşmeyi öğrenmişler. "Nasıl iyi bir şekilde düşelim ki ölmeyelim" diye düşünmüşler. Uçağın icadına giden yol böyleymiş.
Biz de makro yazarken çok sık hata yapacağız, ama bu hataları öyle zarif bir şekilde ele almalıyız ki, bi terslik durumunda yumuşak bir iniş olsun.
İşte bu bölümde program akışı içinde oluşan hataları nasıl ele alacağımızı ve yumuşak iniş yapmayı öğreneceğiz. Zira iyi bir makro içinde mutlaka olası hataları ele alan bir bölüm olmalıdır.

Kodumuzda, oluşabilecek hataları ele aldığımız satırlara Error Handler (Hata Yakalama) satırları denmektedir. Eğer kodumuzda hata yakalama satırı yoksa VBA'in aşağıdaki klasik hata mesajını görürüz. Bu da özellikle programınız kullanacak başka kişiler varsa çok şık olmaz.

post-thumb

Amacımız, kodumuzun uygun yerlerine hata kontrol noktaları koymak, bunların bazısında program akışını uygun yere yönlendirmek, bazısında hatayı düzeltip tekrar başa (veya başka bir yere) döndürmek veya anlaşılır bir mesaj vererek programı sonlandırmak olmalıdır.

Bununla birlikte, geliştirme sırasında hiç bir hata yakalama kullanmamak uygun olabilir, hatta böylesi daha iyidir, böylece çıkan hata mesajlarında "Debug" düğmesine tıklayarak nerede hata aldığımızı görebiliriz. Ayrıca önceki bölümde gördüğümüz gibi Debug.Assert ile de tasarım sırasında da test etmiş oluruz.

post-thumb

Debug'a basınca hatanın olduğu yer sarılı gösteriliyor.

post-thumb

NOT: Kodunuz korumalıysa Debug düğmesi pasif olur.

Hata çeşitleri

Farklı kaynaklarda farklı sayılar verilmekle birlikte ben hataları genel olarak 2 kategoriye ayırıyorum:

Derleme hataları (Compile error)

Programın derlenmesini engelleyen hatalardır. Örnekler:

  • Option Explict açıkken tanımlanmamış değişken kullanımı
  • If bloğunda Then yazmadan bir alt satıra geçmek
  • Next yazılmamış bir For döngüsü
  • Tüm yazım hataları (syntax error): Range yerine Rage yazmak
  • Olmayan bir prosedürü çağırmak
  • Bir prosedüre modülle aynı ismi vermek
  • v.s
Çalışma Zamanı hataları (Runtime error)

Kodunuz sorunsuz derlenmiştir, ancak çalışma sırasında bir yerde hata verebilir. Örnekler:

  • 0'a bölme
  • Bir değişkene taşıyabileceği değerden daha fazla değer atanması (integer'a 50.000 atanması gibi)
  • Olmayan bir sayfaya/dosyaya erişmek
  • v.s

NOT: Yazımsal kaynaklı (syntax) compile hataları sonucunda çıkan dialog kutuları bazen can sıkıcı olabilmektedir. Bunların çıkmasını engelleyebilirsiniz, tabi yine de hatalı olan kod kırmızı olarak gösterilecektir. Böylece hatanızı hala anında görmeye devam edersiniz. Bununla sadece dialog kutusunda kurtulmuş oluyoruz. Baktınız ki bir türlü hatanın nedenini çözemiyorsunuz, bu işlemi geriye alıp tekrar mesaj çıkmasını sağlayabilirsiniz.

post-thumb

Derleme hatası olup olmadığını Debug>Compile menüsünden test edebilirsiniz. Varsa tekrar yapın, taa ki bu düğme gri, yani pasif olana kadar.

Runtime hatalarını On Error ile başlayan cümleciklerle ele alacağız. Bunlara geçmeden önce bir de beklenti türüne göre hataları inceleyelim.

Beklentiye göre Hata çeşitleri

Hatalar beklentiye göre iki gruba ayrılır:

  • Beklenen hatalar
  • Beklenmeyen hatalar

Program akışı sırasında bir yerde hata olma olasılığı varsa, yani hata beklenen bir hataysa bunları koşullu yapılarla yakalayabiliriz.

Örneğin bir metin dosyasından okuma yapmadan önce (veya bir excel dosyasını açmadan önce) onun varolup olmadığını öğrenmeye çalışmak akıllıca olur. Dosya varsa okuma yaparız, yoksa yapmayız ve kullanıcıya bir mesaj çıkarırız. Böyle spesifik bir beklentiye karşı spesifik bir mesaj vermiş oluruz.

                            
                                    Sub OpenFile()
                                        Dim dsy As String
                                        dsy = "C:\deneme.txt"

                                        ' DosyaVarmı diye bir fonksiyon yazmış olalım, bu fonksiyon ile onun varlığını sorguluyoruz
                                        If Not DosyaVarmı(dsy) Then 'DosyaVarmı(dsy)=False da yazabilirdik
                                            MsgBox "Dosya mevcut değil, dosya adının doğru olduğundan emin olup tekrar deneyin"
                                            Exit Sub
                                        Else
                                            'yapılacak işler
                                        End If
                                    End Sub
                            
                        

On Error kalıbı hata yakalamanın kalbidir ve bu kalıbı takip eden bir dizi ifade vardır. Bunlar, "hata olması durumunda şunu yap" şeklinde özetlenebilecek ifadelerdir.

On Error Resume Next

Bu kalıp, VBA'e hatayı görmezden gelmesini ve bir sonraki satıra geçmesini söyler. Yani hatayı düzeltmek yerine yola devam eder. O yüzden çok dikkat edilmesi gereken bir kalıptır. Birçok durumda bunu kullanmaktan sakınmanız gerekir, bütün üstatlar da bunu söyler zaten, ancak belli bazı durumlar vardır ki bu kalıbı kullanmak akıllıca olmaktadır.

Mesela bankada çalışıyoruz diyelim ve bir otomatik mail makromuz olsun. Bankadaki belli bir roldeki tüm personele, kişiye özel olacak şekilde mail gönderiyor olalım. Ancak elimizdeki kişi listesi güncel olmayabilir, yani bu kişilerden bazıları artık bankadan ayrılmış olabilir. Böyle bir durumda programa şunu söyleyebiliriz: "Sırayla herkese mail at, maili bulunmayanlar için durma, bir sonrakine devam et". Bunu demezsek, program, ilk ayrılmış kişide durur. Bunu ele almanın başka yöntemleri de var tabi; Resolve metodunu kullanmak, On Error GoTo etiket diyip, ilgili kaydı formatlamak ve bir sonrakine devam etmek gibi ama konuyu anlatmak adına basit düşünüyoruz.

İkinci bir örnek, her gün belirli bir saate schedule edilmiş ve o günün sonucunu tarih adıyla kaydeden bir rutininiz olsun. Bir süre sonra diskinizde şişkinlik olmasın diye her gün t-30 tarihli raporları silmeye yarayan da başka bir rutininiz olsun. Yani bize sadece son 30 günün raporu yeter diyoruz. Ama bazı günler raporlarınız oluşmamış olabilir, o yüzden varolmayan bir t-30 raporunu silmeye çalışabilirsiniz. Böyle günlük schedue edilmiş 40 raporunuzun ikisi için rapor oluşmadığını düşünelim. Diğer 38ini tehlikeye atmamak için yine bu kalıbı kullanabiliriz. Tabi bunu bir alternatifi de dosya mevcut mu diye kontrol etmek de olabilir ama bu kontrol de kodun süresini uzatacaktır. Böyle bir durumda bu kalıbı kullanmak daha pratik bir çözüm olacaktır.

Aşağıda ilk örneğimize ait kod bloğunu görüyoruz.

                            
                                Sub onerrorresumeornek()
                                    'Dim ile değişken deklerasyonları

                                    '1000 diye sabit bir rakam olmaz tabi, bunu son satır gibi bir yöntemle yakalamanız lazım _
                                    örnek basit olsun diye 1000 yazdım
                                    For i = 1 To 1000 

                                    On Error Resume Next 'sadece döngü içinde hata olursa bir sonraki satıra geçsin

                                    'mail gönderim kodları

                                    Next i

                                    'varsa diğer kodlar

                                End Sub
                            
                        
On Error GoTo Etiket

Bu kalıp, tüm hata türlerini yakalar ve programı etiketin olduğu yere yönlendirir. Bu kısımda genelde şunlar yapılır:

  • Hata mesajları veririz
  • Olası hatayı düzeltebiliyorsak düzeltir ve hata aldığımız yere veya hemen sonraki satıra yönlendiririz
  • Programın başında False atadığımız bazı özellikleri burada tekrar True'ya döndürürüz
  • Sayfa veya Workbook seviyesinde protection'ı geçici kaldırdıysak tekrar protection uygularız
  • Log tutarız

Etiketin hemen öncesinde bir Exit Sub ifadesinin olmasında fayda vardır, yoksa hatasız ilerleyen kod son olarak buraya gelir ve gereksiz yere buradaki kodları da çalıştırır, ki bunu istemeyiz.

Hata mesajında genelde Err.Description ve Err.Number özelliklerini kullanırız. Err.Number için bir koşullu yapı kullanılarak, "Hata numarası şuysa şu mesajı, buysa bu mesajı, diğer durumlar için Err.Description & "Volkanla görüşün" " gibi bir mesaj verdirebiliriz.

On Error kalıbı hata yakalamanın kalbidir ve bu kalıbı takip eden bir dizi ifade vardır. Bunlar, "hata olması durumunda şunu yap" şeklinde özetlenebilecek ifadelerdir.

On Error Resume Next

Bu kalıp, VBA'e hatayı görmezden gelmesini ve bir sonraki satıra geçmesini söyler. Yani hatayı düzeltmek yerine yola devam eder. O yüzden çok dikkat edilmesi gereken bir kalıptır. Birçok durumda bunu kullanmaktan sakınmanız gerekir, bütün üstatlar da bunu söyler zaten, ancak belli bazı durumlar vardır ki bu kalıbı kullanmak akıllıca olmaktadır.

Mesela bankada çalışıyoruz diyelim ve bir otomatik mail makromuz olsun. Bankadaki belli bir roldeki tüm personele, kişiye özel olacak şekilde mail gönderiyor olalım. Ancak elimizdeki kişi listesi güncel olmayabilir, yani bu kişilerden bazıları artık bankadan ayrılmış olabilir. Böyle bir durumda programa şunu söyleyebiliriz: "Sırayla herkese mail at, maili bulunmayanlar için durma, bir sonrakine devam et". Bunu demezsek, program, ilk ayrılmış kişide durur. Bunu ele almanın başka yöntemleri de var tabi; Resolve metodunu kullanmak, On Error GoTo etiket diyip, ilgili kaydı formatlamak ve bir sonrakine devam etmek gibi ama konuyu anlatmak adına basit düşünüyoruz.

İkinci bir örnek, her gün belirli bir saate schedule edilmiş ve o günün sonucunu tarih adıyla kaydeden bir rutininiz olsun. Bir süre sonra diskinizde şişkinlik olmasın diye her gün t-30 tarihli raporları silmeye yarayan da başka bir rutininiz olsun. Yani bize sadece son 30 günün raporu yeter diyoruz. Ama bazı günler raporlarınız oluşmamış olabilir, o yüzden varolmayan bir t-30 raporunu silmeye çalışabilirsiniz. Böyle günlük schedue edilmiş 40 raporunuzun ikisi için rapor oluşmadığını düşünelim. Diğer 38ini tehlikeye atmamak için yine bu kalıbı kullanabiliriz. Tabi bunu bir alternatifi de dosya mevcut mu diye kontrol etmek de olabilir ama bu kontrol de kodun süresini uzatacaktır. Böyle bir durumda bu kalıbı kullanmak daha pratik bir çözüm olacaktır.

Aşağıda ilk örneğimize ait kod bloğunu görüyoruz.

                        
                                    Sub onerrorresumeornek()
                                        'Dim ile değişken deklerasyonları

                                        '1000 diye sabit bir rakam olmaz tabi, bunu son satır gibi bir yöntemle yakalamanız lazım _
                                        örnek basit olsun diye 1000 yazdım
                                        For i = 1 To 1000 

                                        On Error Resume Next 'sadece döngü içinde hata olursa bir sonraki satıra geçsin

                                        'mail gönderim kodları

                                        Next i

                                        'varsa diğer kodlar

                                    End Sub
                                        
                                        
On Error GoTo Etiket

Bu kalıp, tüm hata türlerini yakalar ve programı etiketin olduğu yere yönlendirir. Bu kısımda genelde şunlar yapılır:

  • Hata mesajları veririz
  • Olası hatayı düzeltebiliyorsak düzeltir ve hata aldığımız yere veya hemen sonraki satıra yönlendiririz
  • Programın başında False atadığımız bazı özellikleri burada tekrar True'ya döndürürüz
  • Sayfa veya Workbook seviyesinde protection'ı geçici kaldırdıysak tekrar protection uygularız
  • Log tutarız

Etiketin hemen öncesinde bir Exit Sub ifadesinin olmasında fayda vardır, yoksa hatasız ilerleyen kod son olarak buraya gelir ve gereksiz yere buradaki kodları da çalıştırır, ki bunu istemeyiz.

Hata mesajında genelde Err.Description ve Err.Number özelliklerini kullanırız. Err.Number için bir koşullu yapı kullanılarak, "Hata numarası şuysa şu mesajı, buysa bu mesajı, diğer durumlar için Err.Description & "Volkanla görüşün" " gibi bir mesaj verdirebiliriz.

                        
                            Sub Hataornek()
                                On Error GoTo hata

                                With Application
                                    .DisplayAlerts = False
                                    .ScreenUpdating = False
                                End With

                                'çeşitli kodlar

                                'program bitişi öncesinde bu ayarları eski haline getiriyoruz
                                With Application
                                    .DisplayAlerts = True
                                    .ScreenUpdating = True
                                End With

                                'hata etiketinden hemen önce programdan çıkıyoruz
                                Exit Sub
                            hata:
                                'hata durumunda bu ayarları eski haline getiriyoruz
                                With Application
                                    .DisplayAlerts = True
                                    .ScreenUpdating = True
                                End With
                                MsgBox Err.Description & vbCrLf & "Bu bilgi yeterli değilse Volkanla görüşün lütfen"
                            End Sub
                                
                                

DIKKAT: Bu kalıbı kullanırken akılda bulundurulması gereken önemli bir husus var, o da bir anda sadece tek bir hata yakalama bloğunun aktif olabileceğidir. Yani bir hata için On Error GoTo hata1 dediniz diyelim, hata1 bloğunda da bir hata meydana gelirse oraya da On Error GoTo hata2 yönlendirmesi yaparsanız bu etkisiz kalacaktır, çünkü ilk hata yakalayıcı hala aktiftir. Bu durumu aşmanın bir yolu var ve biraz sonra bunu göreceğiz. Siz şimdi sadece, bunun gerçekten işe yaramadığını görmek için şu kodu çalıştırmayı deneyin.

Bu kodda, önce 0'a bölme hatası yapıyoruz. Bu bizi hata1 etiketine yönlendiriyor. Hata1 Error Handler'ında da, bu hatayı erişimimiz olmayan bir dosyaya yazmaya ve kaydını tutmaya çalışıyoruz. Yine hata alınca bu sefer hata mesajı görüntüleniyor.

                             
                                Sub cokluhata()
                                    On Error GoTo hata1

                                    x = 0
                                    MsgBox 1 / x
                                    MsgBox "İşlem sonucu başarıyla gösterildi"

                                    Exit Sub
                                hata1:
                                    On Error GoTo hata2

                                    dosya = "C:\deneme.txt" 'bu dosyayı yönetici izni sağlayarak oluşturdum, yani dosya var,
                                    'ancak izin isteyen bir konum olduğu için dosyaya yazma sırasında hata alacağım
                                    Open dosya For Append As #1
                                    Write #1, Now, Err.Number, Err.Description, Environ("username")
                                    Close #1

                                    Exit Sub
                                hata2:
                                    MsgBox Err.Description, , "Hata"

                                End Sub
                            
                           
On Error GoTo 0

Hiçbir hata yakalama kodu yazmazsak, VBA ilk hatada bize hata mesajı gösterir demiştik. İşte On Error GoTo 0 da, VBA'in default modu olan "Hata yakalama bloğu yok, tüm hatalar için uyarı göster"e dönüş yapmamızı sağlar. Böyle bir şeyi neden isteyelim ki? Birazdan göreceğiz. Ama şunu anlamışsınızdır ki, bu kalıbı direkt programın başında kullanmak anlamsızdır, zaten default ayar öyledir.

Bu kalıbı daha çok, var olan bir hata yakalama rutini iptal etmek için kullanırız. Neden böyle bir şeye ihtiyaç duyacağımızı bir düşünün! Sonra devam edin.

Mesela yukarıda bahsettiğimiz otomatik mail gönderim makrosunu düşünün. Yaklaşık 1000 kişiye mail gidecek, ama gönderim yapacağınız kişilerden bazılarına mail gönderilemiyorsa bir sonrakine geçsin demiştik. Bunun için de On Error Resume Next demiştik. Ancak mail gönderememe dışında başka bir sorun varsa bunun size veya kullanıcıya gösterilmesini istemiş olabilirsiniz. Ne tür hatalar çıktığını gördükten sonra bunlara özgü çözümlerinizi de ayarlayabilesiniz diye On Error GoTo kalıbını kullanabilirsiniz. Sonrasında ya If blokları ile yakalarsınız veya bir etikete göndererek işlem yaparsınız. Örnek bir kod bloğu şöyle olacaktır:

                        
                        Sub onerrorgotosfır()
                            'Dim ile değişken deklerasyonları

                            '1000 diye sabit bir rakam olmaz tabi, bunu son satır gibi bir yöntemle yakalamanız lazım _
                            örnek basit olsun diye 1000 yazdım
                            For i = 1 To 1000 

                            On Error Resume Next 'sadece döngü içinde hata olursa bir sonraki satıra geçsin

                            'mail gönderim kodları

                            ooMail.Send
                            'On Error GoTo 0 buraya da konabilir, aşağıya da

                            Next i

                            'buradan sonra bir hata çıkarsa hata mesajını görelim
                            On Error GoTo 0

                            'diğer kodlar

                        End Sub
                            
                            
On Error GoTo -1

Yukarıda bir programda sadece bir adet hata yakalama bölümü aktif olabilir demiştik, dolayısıyla bir Error Handler bloğunda başka bir Error handlera yönlendirme yapılırsa bu işe yaramıyordu. İşte On Error GoTo -1 ile bu sorunu aşmış oluyoruz. Bu kalıp, var olan Error Handler'ı resetler (Err nesnesini yok eder) ve sizin yeni bir Error handler yaratmanıza imkan verir. (Bunun, aşağıda anlatacağımız Err.Clear ile karıştırılmaması lazım. Err.Clear sadece hata bilgilerini yok eder, Err nesnesini değil). Sözün özü; bu kalıp, başka bir Error Handler bloğu içinde kullanılır ve var olan Error handler içinden başka bir Error handlera yönlendirmenin tek yolu budur. On Error Resume Next bile burada işe yaramaz.

Şimdi yukarıdaki örneği tekrar alalım ve hata1'den hemen sonra kalıbımızı yerleştirelim.

                        
                                    Sub cokluhata()
                                        On Error GoTo hata1

                                        x = 0
                                        MsgBox 1 / x
                                        MsgBox "İşlem sonucu başarıyla gösterildi"

                                        Exit Sub
                                    hata1:
                                        On Error GoTo -1 'bu satırın başına yorum işareti koyarak etkisini görün
                                        On Error GoTo hata2

                                        dosya = "C:\deneme.txt" 'bu dosyayı yönetici izni sağlayarak oluşturdum, yani dosya var,
                                        'ancak izin isteyen bir konum olduğu için dosyaya yazma sırasında hata alacağım
                                        Open dosya For Append As #1
                                        Write #1, Now, Err.Number, Err.Description, Environ("username")
                                        Close #1

                                        Exit Sub
                                    hata2:
                                        MsgBox Err.Description, , "Hata"
                                    End Sub
                                
                                
İkisi bir arada örnek

Inputbox konusunu anlatıkren görmüştük ki, dönüş tipi Range olan bir Inputbox sorusuna Cancel diyip çıkarsak program hata alıyordu. Bunun için "On Error Resume Next" diyerek hata mesajını yokediyorduk. Peki ya sonraki hatalarda devam etmesini istemiyorsak, o zaman yeni bir hata yakalama bloğu set etmeliyiz. Ör:

                            
                                 Sub hataikili()
                                    Dim a As Range

                                    On Error Resume Next 'geçici olarak tanımlanır
                                    Set a = Application.InputBox("Bir hücre seçin", Type:=8) 'buna esc dersek diye, commenteleyip görelim

                                    On Error GoTo hata 'esas hata yakalama kodumuzu yazıyoruz
                                    If Not a Is Nothing Then
                                        MsgBox "Seçim yapıldı"
                                        'Diğer kodlar buraya
                                    Else
                                        MsgBox "Bir seçim yapılmadan çıkmayı tercih ettiniz"               
                                    End If

                                    MsgBox 1 / 0
                                    MsgBox 2 / 4 'buraya ulaşmaz

                                    Exit Sub
                                    hata:
                                        MsgBox Err.Description
                                    End Sub

                            
                        
Kapsamlı bir örnek

Şimdiki örnekte 4 çeşit On Error ifadesini de kullanıyoruz. Bu açıdan bu örneğin çok faydalı olacağını düşünüyorum. Özeti şu:Networkte duran ve her gece refresh olması beklenen bir dosya schedule edilmiş durumda. Schedule saati gelmiş ve dosya açılınca onun içindeki ThisWorkbook_Open makrosu devreye girecek ve dosyadaki tablolar refresh olacak. Refresh sonucunda data dönüyorsa(yani veritabanına ilgili data yüklenmişse) mailing işlemine devam edecek, henüz data yüklenmediyse dosyayı kaydetmeden çıkacak. Aradaki detaylar kafanızı karıştırmasın, sadece hata bloklarına odaklanın.

                            
                        
                                                        Sub musteri_degisim()
                                    Dim OutApp As Object
                                    Dim OutMail As Object
                                    Dim alicilar As Object
                                    Dim scl As Range
                                    Dim rng As Range
                                    Dim alan As Range


                                    Workbooks.Open Filename:= _
                                            gunlukyol + "\Müşterisi değişen miyler\Müşterisi_Değişen_Miyler.xlsm"

                                    'outlook nesnemizi yaratıyoruz
                                    Set OutApp = CreateObject("Outlook.Application")

                                    'ilk hata yakalama kodumuzu yazıyoruz
                                    On Error GoTo cleanup

                                    'kodlarımız hızlı çalışsın diye ayarlamalarımızı yapıyoruz
                                    With Application
                                       .EnableEvents = False
                                       .ScreenUpdating = False
                                    End With

                                    'ANA kod bloğu burada başyor, 
                                    Sheets(1).Select

                                    If IsEmpty(Cells(2, 1)) Then
                                       ActiveWorkbook.Close savechanges:=True
                                       Exit Sub 'hiç kayıt gelmediyse çıksın
                                    End If

                                    For i = 2 To Cells(1, 1).End(xlDown).Row
                                        Cells(i, 2).Select
                                        Set OutMail = OutApp.CreateItem(0)
                                        Set alicilar = OutMail.Recipients.Add(ActiveCell.Value)

                                        If ActiveCell.Value = ActiveCell.Offset(1, 0).Value Then
                                        'yani hem giren hem çıkan varsa
                                            If ActiveCell.Offset(0, 1).Value = "Portföye Giren" Then

                                            'ikinci hata bloğu, hata olma ihtimali var, çünkü mail gönderemye çalışacağım kişi bankadan istifa etmiş olabilir
                                            ' ve mail önderimi sırasında hata oluşabilir, o yüzden hata oluşursa devam et diyorum
                                            On Error Resume Next
                                            With OutMail
                                                .SentOnBehalfOfName = "BBSatisPerformans@akbank.com"
                                                .Subject = "Portföyünüze giren ve portföyünüzden çıkan müşteriler hakkında"
                                                .htmlbody = "Değerli Miyimiz," & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "Dün itibarıyle portföyünüze " & ActiveCell.Offset(0, 2).Value & " adet müşteri girişi olmuştur. Bu müşterilerin 2 gün önceki TMV bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(0, 3).Value), 0, ActiveCell.Offset(0, 3).Value)) & " TL, Kredi bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(0, 4).Value), 0, ActiveCell.Offset(0, 4).Value)) & " TL'dir. (Bu listeye, hesap devriyle gelen ve yeni yaratılan mbbler dahil değildir.)" & Chr(14)
                                                .htmlbody = .htmlbody + "Ayrıca portföyünüzden " & ActiveCell.Offset(1, 2).Value & " adet müşteri çıkışı olmuştur. Bu müşterilerin 2 gün önceki TMV bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(1, 3).Value), 0, ActiveCell.Offset(1, 3).Value)) & " TL, Kredi bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(1, 4).Value), 0, ActiveCell.Offset(1, 4).Value)) & " TL'dir." & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "MBB detayına ulaşmak için OPERA'dan 'Müşterisi değişen Miyler' raporuna bakabilirsiniz." & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "Bilginizi rica ederiz." & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "Saygılarımızla, " & Chr(14)
                                                .htmlbody = .htmlbody + "" & "Bireysel Bankacılık Satış Yönetimi "
                                                alicilar.Resolve
                                                If Not alicilar.Resolved Then
                                                    GoTo sonraki
                                                End If
                                                .Send
                                            End With

                                            Else ' ilk satır portföyden çıkansa

                                            With OutMail
                                                .SentOnBehalfOfName = "BBSatisPerformans@akbank.com"
                                                .Subject = "Portföyünüze giren ve portföyünüzden çıkan müşteriler hakkında"
                                                .htmlbody = "Değerli Miyimiz," & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "Dün itibarıyle portföyünüzden " & ActiveCell.Offset(0, 2).Value & " adet müşteri çıkışı olmuştur. Bu müşterilerin 2 gün önceki TMV bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(0, 3).Value), 0, ActiveCell.Offset(0, 3).Value)) & " TL, Kredi bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(0, 4).Value), 0, ActiveCell.Offset(0, 4).Value)) & " TL'dir." & Chr(14)
                                                .htmlbody = .htmlbody + "Ayrıca portföyünüze " & ActiveCell.Offset(1, 2).Value & " adet müşteri girişi olmuştur. Bu müşterilerin 2 gün önceki TMV bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(1, 3).Value), 0, ActiveCell.Offset(1, 3).Value)) & " TL, Kredi bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(1, 4).Value), 0, ActiveCell.Offset(1, 4).Value)) & " TL'dir. (Bu listeye, hesap devriyle gelen ve yeni yaratılan mbbler dahil değildir.)" & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "MBB detayına ulaşmak için OPERA'dan 'Müşterisi değişen Miyler' raporuna bakabilirsiniz." & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "Bilginizi rica ederiz." & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "Saygılarımızla, " & Chr(14)
                                                .htmlbody = .htmlbody + "" & "Bireysel Bankacılık Satış Yönetimi "
                                                alicilar.Resolve
                                                If Not alicilar.Resolved Then
                                                   GoTo sonraki
                                                End If
                                                .Send
                                            End With

                                            'istifa eden kişiye mail gönderme hatası dışında bir hata olursa 
                                            'bu hatayı greyim ki düzlteyim istiyroum; tabi ekran o an takılı kalır, sornaki schdeul rogramlar aksıya alınır 
                                            'ama olsun, hatayı düzeltmek adıan bunu yapmaız lazım
                                            On Error GoTo 0
                                            End If
                                        i = i + 1
                                        Else 'yani tek satır varsa
                                            'yukardaki nedenlerin aynı sebeple resume next
                                            On Error Resume Next
                                            With OutMail
                                                .SentOnBehalfOfName = "BBSatisPerformans@akbank.com"
                                                .Subject = "Portföyünüze giren ve portföyünüzden çıkan müşteriler hakkında"
                                                .htmlbody = "Değerli Miyimiz," & Chr(14) & Chr(14)
                                                If ActiveCell.Offset(0, 1).Value = "Portföye Giren" Then
                                                   .htmlbody = .htmlbody + "Dün itibarıyle portföyünüze " & ActiveCell.Offset(0, 2).Value & " adet müşteri girişi olmuştur. Bu müşterilerin 2 gün önceki TMV bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(0, 3).Value), 0, ActiveCell.Offset(0, 3).Value)) & " TL, Kredi bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(0, 4).Value), 0, ActiveCell.Offset(0, 4).Value)) & " TL'dir. (Bu listeye, hesap devriyle gelen ve yeni yaratılan mbbler dahil değildir.)" & Chr(14) & Chr(14)
                                                Else
                                                   .htmlbody = .htmlbody + "Dün itibarıyle portföyünüzden " & ActiveCell.Offset(0, 2).Value & " adet müşteri çıkışı olmuştur. Bu müşterilerin 2 gün önceki TMV bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(0, 3).Value), 0, ActiveCell.Offset(0, 3).Value)) & " TL, Kredi bakiyesi " & Int(IIf(IsError(ActiveCell.Offset(0, 4).Value), 0, ActiveCell.Offset(0, 4).Value)) & " TL'dir." & Chr(14) & Chr(14)
                                                End If
                                                .htmlbody = .htmlbody + "MBB detayına ulaşmak için OPERA'dan 'Müşterisi değişen Miyler' raporuna bakabilirsiniz." & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "Bilginizi rica ederiz." & Chr(14) & Chr(14)
                                                .htmlbody = .htmlbody + "Saygılarımızla, " & Chr(14)
                                                .htmlbody = .htmlbody + "" & "Bireysel Bankacılık Satış Yönetimi "
                                                alicilar.Resolve
                                                If Not alicilar.Resolved Then
                                                   GoTo sonraki
                                                End If
                                                .Send
                                            End With
                                            'yine aynı sbeebple goto 0
                                            On Error GoTo 0

                                        End If

                                        sonraki:
                                        Set OutMail = Nothing

                                    Next i


                                    cleanup:
                                    On Error GoTo -1 'Burdada bir hata çıkarsa öncekini resetliyorum
                                    On Error GoTo hata 'hata çıakrsa "hata" etiketine yönlendiriyoru

                                    Set OutApp = Nothing
                                    With Application
                                    .EnableEvents = True
                                    .ScreenUpdating = True
                                    End With

                                    Windows("Müşterisi_Değişen_Miyler.xlsm").Close savechanges:=True

                                    rapor = "Müşteri-Miy değişim"
                                    alici = "13245;32222"
                                    Call Mailat2(rapor, alici)

                                    Exit Sub
                                    hata:
                                    Logcu Now, Err.Description 'Looger isimli loglama proseürümle hata kaydı tutuyorum
                                    End Sub

                            
                        
Error handlerdan çıkma yolları

GoTo -1 bölümünde yaptığım açıklamalar şöyle bir yanlış anlaşılmaya sebep olmamalı. GoTo -1, bir error handler içinden başka bir error handler içine göndermenin tek yoludur, error handler içinden çıkmanın tek yolu değil. Error handler içinden On Error GoTo -1 kalıbına ilave olarak Resume, Resume Next, Resume etiket veya Exit Sub diyerek de çıkabilirsiniz. Bu şekillerde çıkış yapıldığında da Error Nesnesi resetlenmiş olur.

Şimdi bunlara bakalım:

Resume

Resume tek başına kullanıldığında hata olan yerden tekrar devam etmeye çalışır. Bunun için tabi error handlerda hatayı düzeltmeniz gerekir. Duruma göre kullanıcıya düzeltmeyle ilgili bilgi vermek de gerekebilir.

DIKKAT: Hatayı düzeltmeden Resume dersek program sonsuz bir kısırdöngüye girer.

                            
                                    Sub resume1()
                                        On Error GoTo hata
     
                                        x = InputBox("0 ile 100 arasında bir sayı girin") '0 dahil mi belli değil
     
                                        sayı = 1 / x
                                        Debug.Print sayı
     
                                        Exit Sub
                                    hata:
                                        If x = 0 Then
                                            x = 1
                                            MsgBox "Girdiğiniz sayı aralık dışı olup en düşük 1 girmeniz gerekirdi. Sayı otomatikman 1'e yuvarlandı"
                                            Resume
                                        Else
                                            MsgBox Err.Description 'mesela bir harf girilirse hata versin
                                        End If
                                    End Sub
                            
                            

Başka bir örnek de şu olabilir. Biz biliriz ki neredeyse tüm Workbooklarda Sheet1 diye bir dosya vardır, ve kodumuz içinde kullanıcıdan bir dosya seçmesini istedik diyelim, sonra gidip Sheet1 isimli sayfaya bazı şeyleri otomatik yazacağız. Eğer Sheet1 diye bir sayfa yoksa hata verecektir. Bunu aşağıdaki gibi ele alabiliriz.

                                
                            Sub resume3()
                                On Error GoTo hata:
     
                                'Kullanıcıdan dosya seçtiren kodlar
                                Worksheets("Sheet1").Activate
                                'diğer işlemler
                            hata:
                                If Err.Number = 9 Then 'sayfa yoksa
                                    Worksheets.Add.Name = "Sheet1"
                                    Resume 'Worksheets("Sheet1").Activate satırına geri döner
                                End If
                            End Sub
                            
                            

Aşağıdaki örnekte de Error handler içinde hatalı kaydı düzeltip aynı kayıtla (sonraki değil) devam etmesini sağlıyorum:

                        
                            Sub resume4()
                                On Error GoTo hata

                                Dim kareal As Variant
                                kareal = Array(9, 16, -64, 100) 'yanlışlıkla 64'ün önüne - yazmışım diyelim

                                For Each a In kareal
                                    MsgBox a & " sayısının kökü:" & Sqr(a)
                                Next a

                                Exit Sub
                            hata:
                                a = Abs(a)
                                Resume
                            End Sub
                            
                            
Resume etiket

Hata bloğunda gerekli bir mesaj verdikten sonra bir etikete yönlendirme işlemini Resume Etiket kalıbı ile yaparız. Burada alternatif olarak GoTo Etiket kalıbı kullanıldığı görülmekle birlikte bu kullanım beklenmeyen hatalara neden olabilir. Bu yüzden Error handlerlar içindeyken GoTo yerine Resume kullanmanızı öneriyorum.

                        
                            Sub resume2()
                                On Error GoTo hata

                            yeniden:
                                x = InputBox("0 ile 100 arasında bir sayı girin") '0 dahil mi belli değil

                                sayı = 1 / x
                                Debug.Print sayı

                                Exit Sub
                            hata:
                                If x = 0 Then
                                    x = 1
                                    MsgBox "Girdiğiniz sayı aralık dışı olup en düşük 1 girmeniz gerekirdi. Sayı otomatikman 1'e yuvarlandı"
                                    Resume 'burada da istersek kullanıcıya tekrar seçim hakkı vermek isteyebiliriz
                                Else
                                    If Err.Number = 13 Then 'Type Mismatch hatasıysa
                                        MsgBox "Lütfen geçerli bir sayı giriniz"
                                        Resume yeniden 'GoTo etiket de alternatif olmakla birlikte kullanmayın
                                    Else 'başka bir hataysa
                                        MsgBox Err.Description 
                                    End If
                                End If
                            End Sub
                            
                            
Resume Next

Mesela otomatik mail programınızda sicili olmayan kişiler çıkarsa program akışı durmasın diye On Error Resume Next demiştik ya, bunun bir diğer ve şık alternatifi de şu olabilir. On Error Goto hata dersiniz, hata bölümünde bir log kaydı oluşturabilir ve/veya mail gitmeyen kişilerin listesi o an açık olan bir excel dosyasında ise onları renklendirebilirsiniz, böylece kimlere mail gitmediğini de görmüş olursunuz. Zira belki sorun To'ya koyduğunuz kişide değil, cc'deki kişiden kaynaklı da olabilir.

Tabi bu örnekte başka bir hata durumunda istenmeyen sonuçlar elde edebilirsiniz, o yüzden kodunuzu bu duruma göre ayarlamanız gerekecektir. Burada basitlik olması adına sadece olmayan bir adrese mail gitme ihtimali olduğunu düşündük.

                        
                        Sub resumenext1()
                            'Dim ile değişken deklerasyonları
                            On Error GoTo hata

                            '1000 diye sabit bir rakam olmaz tabi, bunu son satır gibi bir yöntemle yakalamanız lazım _
                            örnek basit olsun diye 1000 yazdım
                            For i = 1 To 1000 

                            'mail gönderim kodları

                            Next i

                            'varsa diğer kodlar

                        hata:
                            'Loglama veya formatlama işlemi
                            Resume Next

                        End Sub
                            
                            

Aşağıdaki örnekte ise Error Handlerda hatayı yakaladıktan sonra durmasını istemiyorum ve diğer sayılar için devam etmesini istiyorum, ama devam etmeden önce de bir mesaj vermesini istiyorum.

                        
                            Sub resumenext2()
                                On Error GoTo hata

                                Dim kareal As Variant
                                kareal = Array(9, 16, -1, "A", 81, "B", 100)

                                For Each a In kareal
                                    MsgBox a & " sayısının kökü:" & Sqr(a)
                                Next a

                                Exit Sub
                            hata:
                                MsgBox a & ": Karekökü alınacak bir sayı değil"
                                Resume Next
                            End Sub
                                
                                
Err nesnesi

Bir runtime hatası oluştuğunda Err nesnesi oluşturulur ve bu nesnenin özellikleri bu hatanın detayıyla doldurulur.

Bu nesnenin üyeleri, Exit'li ifadelerden (Exit Sub gibi) ve Resume Next'ten sonra resetlenir, yani 0 ve ""'a döner. Veya manuel resetlemek için Err.Clear metodu kullanılır.

Err'in default property'si (özelliği) Number'dır. Bir de Description vardır. Bunları yukarıdaki örneklerde bolca kullandık. Genelde bu description'daki bilgi son kullanıcıya yeterli bilgi vermez, hele bir de İngilizceleri yoksa bir şey anlamazlar, o yüzden hata numarasına göre kendi mesajlarınızı belirtebilirsiniz.

                        
                        Select Case Err.Number
                            Case Is = 5
                                MsgBox "Negatif sayıların karekökü alınamaz"
                            Case Is = 13
                                MsgBox "Metinsel ifadelerin karekökü alınamaz"
                                    End Select
                            
                            

Çıkan hatanın Number özelliği yoksa Description özelliğinde "Application-defined or object-defined error" mesajı gösterilir. Herhangi bir anda hata yoksa Number=0 ve Description="" değerindedir. O yüzden belirli bir anda hata olup olmadığını If Err.Number=0 ile kontrol edebilirsiniz.

Err.Raise

Err nesnesi oluşturmanın bir yolu da Raise metodunu kullanmaktır. Bu metodla birlikte kendimize özgü Hata Kaynağı, Hata Numarası ve Hata açıklaması üretiriz. Hata numarasını 513 ile 65535 arası bir değer atayabiliyoruz ve bunu vbObjectError ile birlikte kullanıyoruz.

Err.Raise'in bir alternatifi GoTo hata diyerek ilerlemek olabilir ancak bu şekilde hatayı kayıt altına alamayız. Özellikle birden fazla yönlendirme olacaksa GoTo etiket yerine Err.Raise deyip açıklayıcı bilgilerle de donatmak gerekir.

                        
                                Sub errraise()
                                    On Error GoTo hata

                                    x = InputBox("1000'den küçük bir sayı girin") 'çok net değiliz, belki kullanıcı 0 girecek, belki 1000 dahil sanacak

                                    'bunu direkt select case içinde kullanamadığımız için if blok içinde kullanıyorum
                                    If Not IsNumeric(x) Then
                                        Err.Raise 517 + vbObjectError, "Sayısal olmayan veri", "Kullanıcı sayısal olmayan bir değer girdi"
                                    End If

                                    Select Case x
                                        Case 0
                                            Err.Raise 513 + vbObjectError, "x nedeniyle 0'a bölme hatası", "Kullanıcı x=0 girdi"
                                        Case 1000
                                            Err.Raise 514 + vbObjectError, "x nedeniyle 0'a bölme hatası", "Kullanıcı x=1000 girdi"
                                        Case Is > 1000
                                            Err.Raise 515 + vbObjectError, "aralık dışı değer", "Kullanıcı x>0 girdi"
                                        Case Is < 0
                                            Err.Raise 516 + vbObjectError, "aralık dışı değer", "Kullanıcı x<0 girdi"
                                                                                                               Case Else
                                                                                                               sayı=(1 / x) * (1 / (1000 - x))
                                                                                                               End Select
                                                                                                               Debug.Print sayı
                                                                                                               Exit Sub
                                                                                                               hata:
                                                                                                               Debug.Print Err.Source, Err.Number, Err.Description
                                                                                                               End Sub
                                            
                                            
Err.Source

Bu özellik, özellikle Log tutulurken faydalı olabilmektedir, ve yukarıdaki örnekte görüldüğü gibi, Raise ile manuel yaratılan hata mesajlarında bizim tarafımızdan belirtildiğinde anlamlıdır, yoksa her zaman VBAProject gibi bir şey ifade etmeyen bir sonuç üretir. (E neden var o zaman? Çünkü bu özellik VB dilinin kendisinde var ve orada anlamlı ama VBA içinde anlamsızlaşıyor, eğer ki biz kendimiz belirtmezsek!)

Err.Clear

Bu metod yukarıda da gördüğümüz gibi hata nesnesinin üyelerini sıfırlar. Pratik olarak buna ihtiyaç duyabileceğimiz tek yer On Error Resume Next dendiği durumlarda, karşılaşılan hata adedini saymak için kullanmak olacaktır. Zira ben başka bir kullanımını görmedim.

Şöyle ki, döngüsel bir kodumuz var diyelim, döngünün bir yerinde Err.Number<>0 kontrolüne takılır ise hata oluşmuş demektir, bu durumda hatasayısı=hatasayısı+1 diyerek sayacı arttırırız. Ama arada birçok yerde hatasız geçiş olacağı için ama en sonki kod numarası da hala hafızada olacağı için bir kez hata oluştuğunda döngünün sonraki turlarında her yerde tekrar hata kontrolüne takılır. O yüzden sayacı arttırdıktan sonra hata numarasını sıfırlamak gerekir.

Aşağıdaki örnekte, yine otomatik mailing yapacağız ve kaç adet mailin gitmediğini MsgBox ile bildireceğiz. Örneği özellikle basit tuttum, sadece bu kısma odaklanmanız için. Tabii ki yukarıdaki örneklerle birleştirilerek mail gitmeyen satırlar için renklendirme de yapılabilir.

                        
                        On Error Resume Next

                        son = Range("A1").End(xlDown).Row
                        For i = 1 To son
                            'mail kodları

                            If Err.Number <> 0 Then
                                hata = hata + 1
                                Err.Clear
                            End If  

                        Next i

                        MsgBox hata & " adet mail gönderilememiştir"
                            
                            

A1 ve A2 isminde iki prosedürümüz olduğunu düşünelim. A1'in içinde bir yerde A2'yi çağırıyoruz. A2'de bir hata meydana geldiğinde, VBA A2'nin içinde Error Handler var mı diye bakar. Varsa buna girer, yoksa geriye gidip A1'e bakar, A1'de varsa A1'in Error Handler'ına gider.

Aşağıdaki örnekleri F8 ile çalıştırıp görelim. İlk olarak A2'de Error Handler olmadığında A1'deki Error Handler'a geldiğini görelim.

                        
                        Sub A1()
                            On Error GoTo hata

                            MsgBox "selam"
                            Call A2
                            Exit Sub

                        hata:
                            MsgBox "A1'deki hata mesajı"
                        End Sub

                        Sub A2()
                            MsgBox 1 / 0
                        End Sub
                            
                            

Şimdi de A2'de varsa A2'de kaldığını görelim.

                        
                            Sub A1()
                                On Error GoTo hata

                                MsgBox "selam"
                                Call A2
                                Exit Sub

                            hata:
                                MsgBox "A1'deki hata mesajı"
                            End Sub

                            Sub A2()
                                On Error GoTo eh
                                MsgBox 1 / 0
                                Exit Sub

                            eh:
                                MsgBox "A2'deki hata mesajı"
                            End Sub
                                
                                

Tabii bu durum sadece On Error GoTo ifadeleri için geçerlidir, Resume için geçerli değildir.

Aşağıdaki örnekte sadece A1'de Resume satırı var, dolayısıyla A2'de ilk hatayı alır almaz A1'e dönecek ve "devam" mesajını çıkaracak.

                        
                            Sub A1()
                                On Error Resume Next

                                MsgBox "selam"
                                Call A2
                                MsgBox "devam"
                            End Sub

                            Sub A2()
                                MsgBox 1 / 0
                                MsgBox "selam2"
                            End Sub
                                
                                

Halbuki biz A2'de kaldığı yerden devam etmesini istersek A2 içine de ayrıca On Error Resume Next yazmalıyız.

                        
                            Sub A1()
                                On Error Resume Next

                                MsgBox "selam"
                                Call A2
                                MsgBox "devam"
                            End Sub

                            Sub A2()
                                On Error Resume Next
                                MsgBox 1 / 0
                                MsgBox "selam2"
                            End Sub
                                
                                

Yukardaki bazı örneklerde Logcu isminde bir prosedür görmüştük. Bununla ortaya çıkan hataları detay bilgilerle kayıt altına alabilriz. Hata takibi açısından oldukça faydalı bir yöntemdir. Özellikle ortak kullanılan ve ara sıra hata veren bi dosyanız varsa şiddetle tavsiye ederim. Böylece kim hangi aşamada ne yapmış da hata meydana gelmiş bunu görebilrisiniz.(dosya refreshi sırasında mı hata oluştu, bir dosyaya ulaşmaya çalışırken mi ha oluştu v.s)
Tabi bunu sadece hata kaydı tutma amacıyla kullanırsanız onun hakkını vermemiş olursunuz. O yüzden Logcu prosedürünü programın kilit noktalarına yerleştirerek tam bir olay kaydı tutabilirsiniz. Bu konunun detay anlatımı başka bir sayfada olduğu için burada tekrar örneklendiremeye gerek görmüyorum. Buradan bakıp incelenemenizi tavsiye ediyorum.