D Programlama Dili - Çözümler

assert İfadesi ve enforce

  1. Bu programı 06:09 ve 1:2 vererek çalıştırdığınızda hata atmadığını göreceksiniz. Buna rağmen, sonucun doğru olmadığını da farkedebilirsiniz:
    09:06'da başlayan ve 1 saat 2 dakika süren işlem
    10:08'de sonlanır.

    Görüldüğü gibi, 06:09 girildiği halde, çıkışa 09:06 yazdırılmaktadır. Bu hata, bir sonraki problemde bir assert denetimi yardımıyla yakalanacak.

  2. Programa 06:09 ve 15:2 verildiğinde atılan hata, bizi şu satıra götürür:
    string zamanDizgisi(int saat, int dakika) {
        assert((saat >= 0) && (saat <= 23));
        // ...
    }
    

    Saat bilgisinin 0 ile 23 arasında bir değerde olmasını denetleyen bu assert denetiminin başarısız olması, ancak bu işlev programın başka yerinden yanlış saat değeriyle çağrıldığında mümkündür.

    zamanDizgisi işlevinin çağrıldığı sonucuYazdır işlevine baktığımızda bir yanlışlık göremiyoruz:

    void sonucuYazdır(
            int başlangıçSaati, int başlangıçDakikası,
            int işlemSaati, int işlemDakikası,
            int bitişSaati, int bitişDakikası) {
        writef("%s'%s başlayan",
               zamanDizgisi(başlangıçSaati,
                            başlangıçDakikası),
               daEki(başlangıçDakikası));
    
        writef(" ve %s saat %s dakika süren işlem",
               işlemSaati,
               işlemDakikası);
    
        writef(" %s'%s sonlanır.",
               zamanDizgisi(bitişSaati, bitişDakikası),
               daEki(bitişDakikası));
    
        writeln();
    }
    

    Bu durumda sonucuYazdır işlevini çağıran noktalardan şüphelenir ve onun programda main içinden ve tek bir noktadan çağrıldığını görürüz:

    void main() {
        // ...
        sonucuYazdır(başlangıçSaati, başlangıçDakikası,
                     işlemSaati, işlemDakikası,
                     bitişSaati, bitişDakikası);
    }
    

    Çağıran noktada da bir sorun yok gibi görünüyor. Biraz daha dikkat ve zaman harcayarak sonunda başlangıç zamanının ters sırada okunduğunu farkederiz:

        zamanOku("Başlangıç zamanı",
                 başlangıçDakikası,
                 başlangıçSaati);
    

    Programcının yaptığı o dikkatsizlik nedeniyle 06:09 olarak girilen bilgi aslında 09:06 olarak algılanmakta ve daha sonra buna 15:2 süresi eklenmektedir. zamanDizgisi işlevindeki assert de saat değerini 24 olarak görür ve bu yüzden hata atılmasına neden olur.

    Burada çözüm, başlangıç zamanının okunduğu noktada parametreleri doğru sırada yazmaktır:

        zamanOku("Başlangıç zamanı",
                 başlangıçSaati,
                 başlangıçDakikası);
    

    Çıktısı:

    Başlangıç zamanı? (SS:DD) 06:09
    İşlem süresi? (SS:DD) 15:2
    06:09'da başlayan ve 15 saat 2 dakika süren işlem
    21:11'de sonlanır.
    
  3. Bu seferki hata, daEki işlevindeki assert ile ilgili:
        assert(ek.length != 0);
    

    O denetimin hatalı çıkması, da ekinin uzunluğunun 0 olduğunu, yani ekin boş olduğunu gösteriyor. Dikkat ederseniz, 06:09 ve 1:1 zamanlarını toplayınca sonuç 07:10 olur. Yani bu sonucun dakika değerinin son hanesi 0'dır. daEki işlevine dikkat ederseniz, 0'ın hangi eki alacağı bildirilmemiştir. Çözüm, 0'ın case bloğunu da switch ifadesine eklemektir:

        case 6, 9, 0:
            ek = "da";
            break;
    

    Bu hatayı da bir assert sayesinde yakalamış ve gidermiş olduk:

    Başlangıç zamanı? (SS:DD) 06:09
    İşlem süresi? (SS:DD) 1:1
    06:09'da başlayan ve 1 saat 1 dakika süren işlem
    07:10'da sonlanır.
    
  4. Daha önce de karşılaştığımız assert yine doğru çıkmıyor:
        assert((saat >= 0) && (saat <= 23));
    

    Bunun nedeni, zamanEkle işlevinin saat değerini 23'ten büyük yapabilmesidir. Bu işlevin sonuna, saat değerinin her zaman için 0 ve 23 aralığında olmasını sağlayan bir kalan işlemi ekleyebiliriz:

    void zamanEkle(
            int başlangıçSaati, int başlangıçDakikası,
            int eklenecekSaat, int eklenecekDakika,
            out int sonuçSaati, out int sonuçDakikası) {
        sonuçSaati = başlangıçSaati + eklenecekSaat;
        sonuçDakikası = başlangıçDakikası + eklenecekDakika;
    
        if (sonuçDakikası > 59) {
            ++sonuçSaati;
        }
    
        sonuçSaati %= 24;
    }
    

    Yukarıdaki işlevdeki diğer hatayı da görüyor musunuz? sonuçDakikası 59'dan büyük bir değer olduğunda sonuçSaati bir arttırılıyor, ama sonuçDakikası'nın değeri 59'dan büyük olarak kalıyor.

    Belki de şu daha doğru bir işlev olur:

    void zamanEkle(
            int başlangıçSaati, int başlangıçDakikası,
            int eklenecekSaat, int eklenecekDakika,
            out int sonuçSaati, out int sonuçDakikası) {
        sonuçSaati = başlangıçSaati + eklenecekSaat;
        sonuçDakikası = başlangıçDakikası + eklenecekDakika;
    
        sonuçSaati += sonuçDakikası / 60;
        sonuçSaati %= 24;
    
        assert((sonuçSaati >= 0) && (sonuçSaati <= 23));
        assert((sonuçDakikası >= 0) && (sonuçDakikası <= 59));
    }
    

    Aslında sonuçDakikası hâlâ hatalıdır çünkü ona da 60'tan kalanı atamak gerekir. Ama şimdi işin güzel tarafı, artık bu işlevin hatalı saat ve dakika değerleri üretmesi assert denetimleri nedeniyle olanaksızdır.

    Yukarıdaki işlevi örneğin 06:09 ve 1:55 değerleriyle çağırırsanız, sonuçDakikası'nı denetleyen assert denetiminin hata vereceğini göreceksiniz.

  5. Burada sorun, son hanenin 0 olmasından kaynaklanıyor. Son hane sıfır olunca onlar hanesini de katarak "on", "kırk", "elli", vs. diye okuyunca 0'a verilmiş olan "da" eki her durumda doğru çalışmıyor. Bu problemin çözümünü size bırakıyorum.