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.
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.
Debug'a basınca hatanın olduğu yer sarılı gösteriliyor.
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.
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.