6D: Tasarım, Hata Ayıklama ve Kurabiyeler

Bu derste programlarımızı nasıl daha iyi tasarlayabileceğiniz ve hatalarını nasıl daha iyi ayıklayabileceğiniz konusunda bazı tavsiyeler bulacaksınız. Ne kadar çok program yazarsanız hatalardan kaçınma ve hataları düzeltme konusunda o kadar çok deneyim kazanacaksınız, yine de şimdiden işe yarayabilecek genel bazı ipuçları üzerinde duralım. Bu derste şu teknikler hakkında konuşacağız:

  • bir kerede sadece bir bölümü yazın
  • bazı örnekleri kâğıt kalemle yapın
  • kod yazmaya başlamadan önce bir plan yapın
  • kodu yazın
  • kâğıt kalemle çözdüğünüz örnekleri test edin
  • ayrıca rastgele veya az rastlanabilecek örnekler de test edin

Tabii ki bu sihirli formül tüm sorunları otomatik çözmeyecek, ama karşınıza çıkabilecek pek duruma sizi hazırlayacak ve olası problemleri en aza indirecek.

Bir problemle karşılaştığınızda size yardım edebilecek bazı stratejiler; print ifadelerinin teşhis amaçlı kullanımı (bu derste bunu göreceğiz), görüntüleyciyi kullanmak, yazdığınız kodu dikkatlice okumak, ve iyi testler yazmak. Eğer  Python'u evde çalıştırırsanız değişim noktalarını ve adım adım ilerleme araçlarını kullanmak neyin yanlış gittiğini anlamanızda yardımcı olabilir.

Algoritma bir talimatlar dizisidir. Bir programın çalışabilmesi için algoritma Python veya JavaScript gibi bir dilde yazılması gerekir ama kendi dilinizde veya bir diagramla ifade edilebilecek bir talimatlar dizisi de  algoritma olarak adlandırılabilir. Mesela bir çörek tarifi de bir çeşit algoritmadır; "3 bardak un alın" adımlardan biri, "hamuru 2 saat dinlendirin" başka bir adımdır. Bu derste basit bir algoritma geliştireceğiz. Ardından algoritmayı uygulayacağız; yani bu, hazırladığımız talimatlar dizisinin çalışabilir bir bilgisayar programına dönüştürülmesi anlamına gelecek.

Problem: Kurabiye Hesaplayıcı

Tim Horton, Kanada'da timbit adlı top kurabiyelerden üreten bir kahve zinciri:

Photo: looking_and_learning, flickr

Gelecek haftalarda birkaç parti düzenlemeyi planlıyorsunuz ve her parti için farklı sayıda timbit kurabiyesi siparişi vermeniz gerekecek. Bütçenizi planlamak için, istediğiniz sayıda timbitin fiyatını hesaplayan bir program yapmak istiyorsunuz. Sizin mahallenizdeki markette timbit fiyatları da şöyle:

Sayı Fiyat
1 adet 0.20
10 (küçük kutu) 1.99
20 (orta kutu) 3.39
40 (büyük kutu) 6.19

Her defasında bir bölümü yap

Partiye gelen P kişi sayısını input olarak alacak ve vergi dâhil herkese yapılacak ikramın maliyetini output olarak verecek bir program yazmanız gerekiyor. En üst düzeyde, bu programın üç bileşeni vardır:

  1. P sayıda kişi için gereken T sayıda kurabiyeyi bulmak,
  2. T sayıda kurabiye fiyatını bulmak,
  3. vergiyle birlikte genel tutarı hesaplamak.

Bu üç bölümü ayrı ayrı çalışabiliriz. Bir sonraki bölüme geçmeden önce mevcut bölümü yazıp, test etmek ve düzeltmek en iyisidir, çünkü programınız uzadıkça içindeki hataları bulmak ve tamir etmek daha zorlaşır. Hepsini bir araya getirmeden önce her bölümü ayrı ayrı geliştirmek daha kolay olacak. Dersin devamında T sayıda kurabiyenin fiyatını hesaplayacağımız 2. adıma odaklanacağız.

Kâğıt kalemle çözülen bazı örnekler

Bilgisayara bir şey yapmasını söyleyebilmemiz için önce onu kendimiz yapabiliyor olmamız lazım.  Bunu bir örnekle açıklayalım: 45 timbit kurabiyesi almak için kaç para ödememiz gerekiyor?  Kutuların içeriklerine bakarsak en büyük boy kutuyu alabiliriz (40 adet),  fiyatı da 6.19. O zaman bize  45 – 40 = 5 kurabiye daha gerekecek, tek tek aldığımızda fiyatı 5 * 0.20 = 1.00 tutacak. Toplam ödeyeceğimiz para 6.19 + 1.00 = 7.19 oluyor.

Yazmaya başlamadan önce kodun planını yap

Ama 45 yerine mesela 4 veya 456 kurabiye alacak olsak? İhtiyacı mümkün olduğunca büyük kutularla karşılamaya çalışacağız, arta kalanı da orta ve küçük boy kutularla tamamlayacağız.

Bu algoritma en iyi sonucu verecek mi? Evet! 40 kurabiyelik bir büyük kutuyu 6.19'a alabilecekken, 2*20=40 kurabiyeyi iki orta boy kutuyla almak (2*$3.39 = $6.78) ekonomik değil.  Benzer karşılaştırmaları diğer fiyat seçenekleriyle de yapabiliriz, her defasında ihtiyacı büyük boy kutularla karşılamak daha ekonomik oluyor. Yani algoritmamız iyi. Eğer fiyat/miktar farklı olsaydı, algoritmayı ona göre kurmamız gerekecekti.

Kodu yazmaya başlamadan önce plan yapalım demiştik. Şöyle bir plan yapabiliriz:

  • Python'da yazmaya başlamadan önce kendi dilimizde algoritmamızın adımlarını tek tek kısaca tarif edelim
  • adımları resmedecek şekilde bir şema veya diyagram çizelim
  • şimdilik, detaylardan ziyade ana fikre odaklanalım.

Örneğimizde adımları tarif ediyoruz. Bu program için akış şeması vermiyoruz, çünkü zaten adımları sırayla ve tek tek gerçekleştireceğiz. Daha sonra göreceğimiz döngüsel programlarda akış şemaları bize daha çok gerekli olur. 

Herhangi bir sayıda kurabiyenin fiyatını hesaplayacak algoritma

  1. inputa bakarak gereken kurabiye sayısını tespit et
  2. sıfırdan başlayarak, toplam tutarı takip et
  3. mümkün olduğunca çok sayıda büyük kutu satın al
    1. kutular alındıktan sonra kalan kurabiye sayısını hesapla
    2. toplam fiyatı güncelle
  4. kalan kurabiyeleri orta boy kutu alarak tamamla, A ve B adımlarını tekrarla.
  5. kalan kurabiyeleri küçük boy kutu alarak tamamla, A ve B adımlarını tekrarla.
  6. kalan kurabiye sayısını karşılamak için tekli kurabiyelerden al ve B adımını tekrarla.
  7. toplam fiyatın çıktısını ver.

Bu algoritma tarifine yalancı kod (pseudocode) deniyor, çünkü aslında talimatları adım adım açıklasa da hiçbir programlama dilinde anlamlı değil.

Kodu yaz

İlk tavsiyeyi hatırlayalım, bir kerede bir bölüm yazıyoruz; yani, 1'den 7'ye kadar olan her adım için ayrı bir kod parçası yazmayı düşünebiliriz. Ayrıca, tüm adımlar hakkında düşünmemiz gereken bir şey var: Hangi değişkenleri kullanacağız? Adları, türleri ve başlangıç değerleri ne olmalı?

Hâlâ almamız gereken kurabiye sayısı için  kalanTimbit, şu ana kadarki toplam tutarı göstermek için toplamTutar değişkenlerini atayalım. Şimdilik Python'a bu nesnelerin tiplerini söylememize gerek yok, ama biz biliyoruz ki kalanTimbit, kurabiye sayısını vereceği için  int (tamsayı) ve toplamTutar değişkeni de fiyatı göstereceği için float (ondalık sayı) olacak. (Bir kuruş 0.01). Her adım için başka geçici değişkenlere de ihtiyacımız olacak, ama bunlara gerektikçe bakalım.

  1. ve 2. adımları birer satırlık kodla halledebiliriz:
kalanTimbit= int(input()) # 1. adım: inputu al toplamTutar = 0 # 2. adım: toplam fiyatın ilk durumunu belirle
3. adım için, alabileceğimiz en yüksek sayıda büyük kutuyu bulalım. Pekiyi bunu nasıl yapacağız? Büyük boy kutuda 40 kurabiye olduğuna göre büyükKutu sayısı kalanTimbit bölü 40 olmalı. Bölme, fiyat hesaplama, çıkarma işlemlerini takip etmek üzere aşağıdaki kod parçasını yazabiliriz:

# 3. adım: mümkün olduğunca çok sayıda büyük boy kutu satın al
büyükKutu = kalanTimbit / 40
toplamTutar = toplamTutar + büyükKutu * 6.19    # toplam tutarı güncelle
kalanTimbit = kalanTimbit - 40 * büyükKutu # hâlâ gereken kurabiye sayısını bul
Aslında, görünüşe göre sanki şu anda bir hata yaptık. Hatayı bulabilmek için en iyi yol kodunuzu sık sık test etmektir! Diyelim ki kodumuzu bu noktada test etmek istiyoruz, yapmamız gereken birkaç print ifadesi yazmak olacak. (print ifadeleri kullanmak yerine görüntüleyiciden her satırı tek tek kontrol edebilirsiniz.)

Example
Yazdığımız kodu test etmek amacıyla, inputu 45 kabul ederek, hata ayıklamakta print ifadelerini kullanıyoruz.

İstediğimiz gibi gitmediğine göre bir sorun var! Söylediğimiz şeyle, programın yaptığı şeyin farklılaşmaya başladığı ilk satır hangisi? Bir sonraki paragrafa geçmeden önce bunu bulmaya çalışın. Biz bu sorunu en başta kalem kâğıtla çözmüştük aslında.

Açıklamayı okumak için tıklayın
Şu büyükKutu = kalanTimbit / 40, formülümüze göre, input 45 olduğunda büyükKutu sayıs 1.125, olacak, ama aslında 1 olmalıydı, çünkü kutudan bir parça alma şansımız yok. Bir çözüm yolu, 7A dersinde göreceğimiz gibi bölme işareti olan  / yerine //  kullanarak sonucun integer (tamsayı) olmasını sağlamak olabilir. Başka bir yol ise 4. derste gördüğümüz şekilde kalanTimbit / 40 işleminin sonucunu  int tipi ile tanımlayarak ondalık kısmı kesmek. Biz de bu yolu kullanalım ve satırı şöyle düzeltelim: büyükKutu = int(kalanTimbit / 40).

4 ve 5. adımlar 3. adıma benziyor. En fazla bir kutu orta boy veya küçük boy kutu alacağımızdan, bu kez  alternatif olarak,  if ifadesi kullanalım:

if kalanTimbit >= 20: # 4. adım, orta boy bir kutu alabiliyor muyuz?
  toplamTutar = toplamTutar + 3.39
  kalanTimbit = kalanTimbit - 20
if kalanTimbit >= 10: # 5. adım, küçük boy kutu alabilir miyiz?
  toplamTutar = toplamTutar + 1.99
  kalanTimbit = kalanTimbit - 20
Son olarak her birine 20 kuruş ödeyerek  kalan kurabiyeleri de toplama ekleyip sonucu yazdıracağız.

toplamTutar = toplamTutar + kalanTimbit  * 20 # 6. adım
print(toplamTutar)                         # 7. adım

Kâğıt kalemle çözdüğün örnekleri test et

Programımız genel olarak hazır. Yapacağımız ilk şey, inputu 45 kabul ederek kâğıt kalemle çözdüğümüz örneğin çalışıp çalışmadığını test etmek.

Example
45 adet kurabiye ne kadar tutacak?
You may enter input for the program in the box below.

Görünüşe göre bir hata (bug) var; çünkü 45 kurabiye için ücret 100 TL tuttu! Şimdi bu hatayı ve başka gizli hataları bulup düzeltmemiz gerekiyor. Bir sonraki bölümde de bunu test edeceğiz.

Market promosyon yapmış, hata da değil: T+1 kurabiye almak T sayısında kurabiye almaktan daha ucuza geliyor. Mesela 19 kurabiye 3.79 tutarken, 20 kurabiye almak için sadece 3.39 ödeyeceğiz. Buna bağlı olmaksızın, biz T sayıda timbiti mümkün olan en ucuz fiyata alma konusunu çözmeye çalışıyoruz, yani en baştaki algoritmamız doğru bir mantığa oturuyor. Yani input 19  olduğu durumda fiyat 3.79  olarak çıkmalı.

Farklı örnekleri test et

Computer Science Circles burada, problemi doğru çözümleyen kodlar yazdığınızı otomatize olarak test etmeye çalışıyor. Ama gerçek programlamada kendi kodunuzu en ince ayrıntılarına kadar sizin test etmeniz gerekecek. İşinize yarayacak bazı test tavsiyelerimiz var.

  • Programınızdaki her kod satırının doğru çalışıp çalışmadığını kontrol edebilirsiniz.  Timbit probleminde inputu 10  kabul ederek, programın küçük sayılarla nasıl çalışacağına bakılabilir. Veya inputu  20 ve 40 olarak değiştirip orta ve büyük boy kutuları denersiniz. En sonunda, daha az sayılarla, diyelim inputu 1 vs. girip, tek tek kurabiye alımında programın nasıl işlediğini kontrol edersiniz.
    • Bu 3.39 ve 1.99 sayılarını doğru yazıp yazmadığınızı kontrol etmeye yarar.
  • Programınızı en sınırda örneklerle test ederek sorunları gözlemleyin. Mesela bu programa girebileceğimiz en düşük input 0, çünkü eksi sayı giremeyiz. En yüksek input için ise sınır yok, ama büyük kutu için test edebileceğimiz sınırda rakamlar 39 ve 41 olabilir.
    • Böylece mesela yanlışlıkla >=  yerine  > yazıp yazmadığınızı kontrol etmiş olursunuz.
  • Bazı durumlarda her inputu veya rastgele (random) sayıda inputu otomatik kontrol ederek programın doğru çalışıp çalışmadığına bakabilirsiniz. Biz CS Circles'ta iç testlerimizi yaparken rastgele (random) inputları çok kullanırız. Progtramın başına import random yazdığınızda, mesela random.randint(10, 100) yazarsanız  10 ve 100  arası rastgele tam sayıları (integer) otomatik test edebilirsiniz.

Hem hataları bulup hem de tüm testleri geçtiniz mi? Haydi bir deneyelim!

Python'da bazı hesaplamalar çok küçük hatalar getiriyor:
Example: Yuvarlama Hatası
İginçtir yukarıdaki kod 0.35 sonucunu vermiyor! Bu Python'un veriyi depolama şeklinden kaynaklanır. Bu gibi küçük hataları önemsemeye gerek yok: Düzenleyici onları yok sayar. Bu konuyu 7B dersinde daha detaylı konuşacağız.

Coding Exercise: Timbit
Hataları tamir edip tüm testleri geçin!
You may enter input for the program in the box below.

Acıktınız mı? O zaman bir sonraki derse geçelim!

10 timbit bir kutu yaparsa, 8 timbit 1 timbyte yapar. Photo: thisisbrianfisher, flickr