Jump to content
Üyelik kaydınızı yaparak son yazılan içeriklerden haberdar olun! ×

PHP

  • entries
    10
  • comments
    0
  • views
    538

Static Analysis - PHP


Doğuhan ELMA

34 views

PHP'nin tip sistemi üzerine bütün bir makale yazdığımda, bunu neden kullanmak istediğinizi tartışmadığımı fark ettim. Topluluğun önemli bir kısmının PHP'nin tip sistemini kullanmaktan hoşlanmadığının farkındayım, bu nedenle hem artılarını hem de eksilerini iyice tartışmak önemli. Bu bölümde yapacağımız şey budur. Bir tür sistemi tarafından sağlanan değeri tartışarak başlayacağız. Birçoğu, daha katı tip sistemine sahip programlama dillerinin daha az çalışma zamanı hatasına sahip olacağını veya hiç olmayacağını düşünüyor. Güçlü bir sistemin hataları önlediğine dair popüler bir söz vardır, ancak bu tamamen doğru değildir. Hataları güçlü bir şekilde yazılmış bir dilde kolayca yazabilirsiniz, ancak iyi bir tür sistemi bunları önlediğinden, hataların bir dizi olması artık mümkün değildir.

 

Güçlü, zayıf, statik ve dinamik tip sistemler arasındaki farkın ne olduğundan emin değil misiniz? Konuyu bu makalenin ilerleyen kısımlarında derinlemesine ele alacağız!

Basit bir fonksiyon düşünün: rgbToHex. Her biri 0 ile 255 arasında tam sayı olan üç bağımsız değişken alır. Daha sonra işlev, tam sayıları onaltılık bir dizeye dönüştürür. Türleri kullanmadan bu işlevin tanımı şu şekilde görünebilir:

function rgbToHex($red, $green, $blue) {
 // …
}

Bu fonksiyonu uygulamamızın doğru olduğundan emin olmak istediğimiz için testler yazıyoruz:

assert(rgbToHex(0, 0, 0) === '000000');

assert(rgbToHex(255, 255, 255) === 'ffffff');

assert(rgbToHex(238, 66, 244) === 'ee42f4');

 

Bu testler her şeyin beklendiği gibi çalışmasını sağlar. Doğru? Peki… 16.777.216 olası RGB kombinasyonundan yalnızca üçünü test ediyoruz. Ancak mantıksal akıl yürütme bize, eğer bu üç örnek durum işe yararsa muhtemelen güvende olacağımızı söylüyor. Tamsayılar yerine kayan sayıları geçersek ne olur?

rgbToHex(1.5, 20.2, 100.1);

Veya izin verilen aralığın dışındaki sayılar mı?

rgbToHex(-504, 305, -59);

Peki ya, null?

rgbToHex(null, null, null);

yada string?

rgbToHex('red', 'green', 'blue');

 

Veya yanlış sayıda argüman mı var?

rgbToHex();
rgbToHex(1, 2);
rgbToHex(1, 2, 3, 4);

 

Yoksa yukarıdakilerin bir kombinasyonu mu? Programımızın yapması gerekeni yaptığına dair göreceli bir kesinlik elde etmeden önce test etmemiz gereken beş uç durumu kolayca düşünebilirim. Bu yazmamız gereken en az sekiz testtir ve eminim siz de birkaç test daha bulabilirsiniz. Bunlar, tip sisteminin kısmen çözmeyi amaçladığı türden problemlerdir ve bu kelimeye kısmen dikkat edin, çünkü ona geri döneceğiz. Girdiyi belirli bir türe göre filtrelersek testlerimizin çoğu geçerliliğini yitirir. Yalnızca tam sayılara izin vereceğimizi varsayalım:

function rgbToHex(int $red, int $green, int $blue)
{
 // …
}

Türleri mevcut tüm girdilerin bir alt kategorisi olarak düşünebilirsiniz; yalnızca belirli öğelere izin veren bir filtredir. int tipi ipucumuz sayesinde artık gerekli olmayan testlere bir göz atalım:

  • Girişin sayısal olup olmadığı
  • Girişin tam sayı olup olmadığı
  • Girişin boş olup olmadığı

Yine de giriş numarasının 0 ile 255 arasında olup olmadığını kontrol etmemiz gerekiyor. Bu noktada PHP'ler de dahil olmak üzere birçok tip sistemin sınırlamalarıyla karşı karşıya kalıyoruz. Elbette int'yi kullanabiliriz, ancak çoğu durumda bu tür tarafından tanımlanan kategori iş mantığımız için hala çok büyüktür: int aynı zamanda -100'ün iletilmesine de izin verir ve bu bizim fonksiyonumuz için bir anlam ifade etmez. Bazı dillerde uint veya "işaretsiz tamsayı" türü bulunur; ancak aynı zamanda çok büyük bir "sayısal veri" alt kümesidir.

Neyse ki, bu sorunu çözmenin yolları var.

Bir yaklaşım "yapılandırılabilir" veya genel türleri kullanmak olabilir, örneğin int<min, max>. Jenerik kavramı pek çok programlama dilinde biliniyor ancak ne yazık ki PHP'de bilinmiyor. Teorik olarak bir tür, tüm iş mantığınızı bilecek kadar akıllı olacak şekilde önceden yapılandırılabilir.

PHP gibi diller genel türlerin esnekliğinden yoksundur ancak tür olarak kullanılabilecek sınıflarımız vardır. Örneğin yapılandırılabilir bir "tam sayıyı" IntWithinRange sınıfıyla temsil edebiliriz:

class IntWithinRange
{
    private int $value;
    public function _construct(int $min, int $max, int $value)
    {
        if ($value <= $min | $value >= $max) {
            throw new InvalidArgumentException('…');
        }
        $this->value = $value;
    }

    // …
}

 

Yani bir IntWithinRange örneğini kullandığımızda, değerinin bir tamsayı alt kümesiyle sınırlandırıldığından emin olabiliriz. Ancak bu yalnızca IntWithinRange oluşturulurken işe yarar. Uygulamada, rgbToHex fonksiyonumuz içinde bunun minimum ve maksimum değerini garanti edemeyiz, yani yalnızca minimum 0 ve maksimum 255 olan IntWithinRange nesnesini kabul ettiğimizi söyleyemeyiz. Bu nedenle yalnızca herhangi bir nesneyi kabul ettiğimizi söyleyebiliriz. IntWithinRange türü:

function rgbToHex(
 IntWithinRange $red,
 IntWithinRange $green,
 IntWithinRange $blue
) {
 // …
}

Bunu çözmek için daha da spesifik bir türe ihtiyacımız var: RgbValue:

class RgbValue extends IntWithinRange
{
    public function __construct(int $value)
    {
        parent::__construct(0, 255, $value);
    }
}

Çalışan bir çözüme ulaştık. RgbValue kullanıldığında testlerimizin çoğu gereksiz hale gelir. Artık iş mantığını test etmek için yalnızca bir teste ihtiyacımız var: "üç RGB geçerli renk verildiğinde, bu işlev doğru HEX değerini döndürüyor mu?" — büyük bir gelişme!

function rgbToHex(RgbValue $red, RgbValue $green, RgbValue $blue)
{
 // …
}

 

Ama Durun…

Tip sistemlerinin iddia edilen faydalarından biri çalışma zamanı hatalarını ve hatalarını önlemekse, o zaman RgbValue'muzla hala bir yere varamıyoruz. PHP bu türü çalışma zamanında kontrol edecek ve program çalışırken bir tür hatası verecektir. Başka bir deyişle: çalışma zamanında, hatta üretim aşamasında bile işler hâlâ korkunç derecede ters gidebilir. Statik analizin devreye girdiği yer burasıdır. Çalışma zamanı tür kontrollerine güvenmek (ve bunların üstesinden gelmek için hatalar atmak) yerine, statik analiz araçları kodunuzu çalıştırmadan test edecektir. Herhangi bir IDE türünü kullanıyorsanız, onu zaten kullanıyorsunuz demektir. IDE'niz size bir nesnede hangi yöntemlerin mevcut olduğunu, bir fonksiyonun hangi girdiyi gerektirdiğini veya bilinmeyen bir değişken kullanıp kullanmadığınızı söylediğinde, bunların hepsi statik analiz sayesindedir. Kabul edelim ki, çalışma zamanı hatalarının hala yararları var: bir tür hatası oluştuğunda kodun daha fazla yürütülmesini durduruyorlar, dolayısıyla muhtemelen gerçek hataları önlüyorlar. Ayrıca geliştiriciye tam olarak neyin yanlış gittiği ve nerede olduğu hakkında yararlı bilgiler sağlarlar. Ama yine de program çöktü. Kodu üretimde çalıştırmadan önce hatayı yakalamak her zaman daha iyi bir çözüm olacaktır. Diğer bazı programlama dilleri, derleyicilerine bir statik analizör dahil edecek kadar ileri giderler: statik analiz kontrolleri başarısız olursa program derlenmez. PHP'nin bağımsız bir derleyicisi olmadığından, bize yardımcı olması için harici araçlara güvenmemiz gerekecek.

PHP Derleyicisi

PHP yorumlanmış bir dil olmasına rağmen yine de bir derleyicisi vardır. PHP kodu, örneğin bir istek geldiğinde anında derlenir. Elbette bu süreci optimize etmek için önbelleğe alma mekanizmaları vardır, ancak bağımsız bir derleme aşaması yoktur. Bu, PHP geliştirmenin iyi bilinen güçlü yanlarından biri olan, bir programın derlenmesini beklemenize gerek kalmadan kolayca PHP kodu yazmanıza ve sayfayı hemen yenilemenize olanak tanır.

 

Neyse ki PHP için topluluk odaklı harika statik analizörler mevcut. Bunlar, kodunuza ve onun tüm tür ipuçlarına bakan, kodu hiç çalıştırmadan hataları keşfetmenize olanak tanıyan bağımsız araçlardır. Bu araçlar yalnızca PHP'nin türlerine değil aynı zamanda belge bloklarına da bakacak, yani normal PHP türlerinden daha fazla esnekliğe izin verecekler. Psalm'ın kodunuzu nasıl analiz edeceğine ve hataları nasıl bildireceğine bir göz atın:

1.png

Burada Psalm'in binden fazla kaynak dosyayı taradığını ve bir işleve doğru miktarda argüman aktarmayı unuttuğumuz yeri tespit ettiğini görüyoruz. Bunu yöntem imzalarını analiz ederek ve bunları bu yöntemlerin nasıl çağrıldığı ile karşılaştırarak yapar. Elbette bu süreçte tür tanımlarının önemli bir rolü vardır. Statik analizörlerin çoğu, örneğin jenerikleri destekleyen özel belge bloğu ek açıklamalarına bile izin verir. Bunu yaparak PHP'nin çalışma zamanında yapabileceğinden çok daha karmaşık tür kontrolleri yapabilirler. Kodu çalıştırırken herhangi bir kontrol yapmasanız bile, statik analiz cihazı bir şeylerin ters gittiğini size önceden söyleyebilir. Bu tür statik tür kontrolleri, kod yazarken yerel olarak, CI işlem hattınıza yerleşik olarak veya her ikisinin bir karışımıyla yapılabilir. Aslında, statik analiz topluluğu bugünlerde o kadar çok ilgi görüyor ki, PHP kodu yazmak için en popüler IDE olan PhpStorm, onlar için yerleşik destek ekledi. Bu, statik analizörünüz tarafından gerçekleştirilen çeşitli tür kontrollerinin sonucunun, kod yazarken hemen gösterilebileceği anlamına gelir. Psalm, PHPStan ve Phan gibi araçlar harikadır ancak aynı zamanda yerleşik dil desteğinden alacağınız etkililikten de yoksundurlar. Önceki makalede yerleşik türler lehine belge bloklarını kaldırma konusunu defalarca anlattım ve şimdi statik analizi desteklemek için bunları yeniden ekliyoruz. Açık olmak gerekirse: bu araçlar aynı zamanda PHP'nin yerleşik tip sistemiyle de çalışır (herhangi bir belge bloğu kullanmadan), ancak bu belge bloğu ek açıklamaları çok daha fazla işlevsellik sunar, çünkü PHP'nin sözdizimi onları sınırlamaz; sonuçta bunlar yorum. Öte yandan (bunu önceki bölümde de söylemiştim), jenerik özellikler gibi özelliklerin yakın zamanda PHP'ye eklenmesi ihtimali çok düşük çünkü bunlar çalışma zamanı performansına büyük bir tehdit oluşturuyor. Dolayısıyla, daha iyi bir şey yoksa, statik analizi tam kapsamıyla kullanmak istiyorsak yine de belge bloklarıyla yetinmek zorunda kalacağız.

 

Keşke… Bununla nereye varacağımı görebiliyor musun? Peki ya PHP genel sözdizimini destekliyorsa ancak bunu çalışma zamanında yorumlamadıysa? Doğruluğu garanti etmek için (jenerikleri kullanırken) bir statik analizör kullanmanız gerekiyorsa ve kodunuzu çalıştırırken bunlar hakkında endişelenmenize gerek kalmazsa ne olur? Statik analizin amacı tam olarak budur. PHP'nin çalışma zamanında bu tip kontrolleri uygulamamasından korkabilirsiniz. Yine de statik analizörlerin, kod çalıştırırken çalıştırılmadıkları için yeteneklerinin çok daha gelişmiş olduğunu iddia edebilirsiniz. Bunun o kadar karmaşık bir fikir olduğunu düşünmüyorum ve aslında diğer diller de bu yaklaşımı zaten kullanıyor. Yıllar geçtikçe popülaritesi muazzam bir şekilde artan TypeScript'i düşünün. JavaScript'e derlenir ve tüm tür kontrolleri bu derleme aşamasında kod çalıştırılmadan yapılır. Şimdi PHP'ye derlenen başka bir dile ihtiyacımız olduğunu söylemiyorum; Sadece statik analizörlerin çok güçlü araçlar olduğunu söylüyorum. Bunları benimsemeye karar verirseniz, test sayısını nasıl azaltabileceğinizi ve çalışma zamanı türü hataların ne kadar nadir meydana geldiğini fark edeceksiniz. Bu bizi şimdi nereye bırakıyor? Ne yazık ki çok uzak değil. Gelişmiş ek açıklamalarını kullanmak isteyip istemediğinize bakılmaksızın projelerinizde bir statik analizör kullanmanızı öneririm. Bunlar olmasa bile statik analizörler büyük fayda sağlar. Tuhaf uç durumların mümkün olmadığından çok daha fazla emin olursunuz ve bu kodu bir kez bile çalıştırmadan daha az test yazmanız gerekir. Araç kutunuzda bulunması gereken harika bir araçtır ve belki bir gün PHP'nin faydalarını tamamen benimsediğini görebiliriz.

Eylemde
Statik analizin sizin için neler yapabileceğini görmeye hazır mısınız? Psalm.dev'e gidip etkileşimli oyun alanlarında oynamanızı tavsiye ederim. Bazı harika örnekler statik analizin tüm gücünü göstermektedir. Laravel için https://github.com/psalm/psalm-plugin-laravel

 

 

 

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...