“Benim makinemde çalışıyor” (İngilizce: “Works on my machine”) — hata kendi yerel ortamında çoğaltamayan ancak hata ekip üyelerinde veya prodüksiyonda istikrarlı bir şekilde görülen geliştiricinin klasik ifadesi. Bu durum, geliştiricinin makinesi ile hatanın çoğaltıldığı ortam arasındaki yapılandırma, bağımlılık sürümleri, işletim sistemi veya veri farklılıklarından kaynaklanır. Stack Overflow Survey 2023'e göre, geliştiricilerin %58'i bu ifadeyi ayda en az bir kez, %31'i ise haftalık olarak söylüyor. Kodun her yerde aynı şekilde çalışmamasının nedenlerini ve ortamın nasıl standartlaştırılacağını anlıyoruz.
Ana noktalar
“Benim makinemde çalışıyor” — bir ekip arkadaşı veya testçi hata bildirdiğinde geliştiricinin söylediği, ancak geliştiricinin makinesinde çoğaltılamayan ifade. Dışarıdan sorunu inkar gibi görünse de teknik olarak durum gerçektir: kod bir ortamda çalışıp diğerinde başarısız olabilir. Yapılandırmadaki tek bir bit farkı, uygulamanın davranışını kökten değiştirir.
Bu ifade BT topluluğunda bir meme haline gelmiştir çünkü aynı anda hem doğru hem de işe yaramazdır. Geliştirici açısından — kod gerçekten onun makinesinde çalışır. Ekip açısından — sorun mevcuttur ve çözülmesi gerekir, meşrulaştırılması değil. Durumun mizahı, geliştiricinin doğruyu söylemesi ancak bu do’runun hatayı düzeltmeye yardımcı olmamasıdır. Meme o kadar popülerdir ki Reddit, XKCD ve DevOps konferanslarında binlerce gönderi ona ayrılmıştır.
Süreç açısından, “benim makinemde çalışıyor” ifadesi ortamın tekrarlanabilirliğiyle ilgili sorunların bir göstergesidir. İki geliştirici aynı kodda aynı sonucu alamıyorsa — ortam kurulum süreci standartlaştırılmamıştır. DevOps uygulaması şunu belirtir: ortam, manuel işlemler olmadan depodan tek bir komutla tekrarlanabilir olmalıdır.
Geliştiricinin yerel ortamı neredeyse her zaman prodüksiyondan farklıdır. Geliştirici macOS veya Windows kullanırken sunucu Linux üzerinde çalışır. Farklı işletim sistemleri farklı dosya sistemlerine, kodlamalara, iş parçacığı zamanlamalarına ve sistem çağrılarına sahiptir. Her iki ortam da Linux olsa bile — çekirdek, glibc, OpenSSL sürümleri farklı olabilir.
İkinci neden — kurulu yazılım seti. Geliştiricinin makinesinde Node.js 20'nin küresel bir sürümü kurulu olabilirken CI/CD yapılandırmasında sürüm 18 belirtilmiş olabilir. Veya geliştirici yerelde PostgreSQL 16 kullanırken prodüksiyonda PostgreSQL 14 kullanıyor olabilir. Alt sürümlerdeki farklılıklar genellikle fark edilmez, ancak ana güncellemeler SQL sorgularının davranışını değiştirebilir. npm Inc.'e göre, bağımlılıkla ilgili hataların %67'si yama sürümlerindeki farklılıklardan kaynaklanır.
Üçüncü neden — ağ koşulları. Yerel makinede gecikme, bant genişliği sınırı veya DNS sorunu yoktur. Prodüksiyonda, harici bir API'ye yapılan herhangi bir istek 5 ms yerine 500 ms sürebilir. Zaman aşımları, yeniden deneme mantığı, yarış koşulları — tüm bu sorunlar yalnızca gerçek yük altında ve gerçek ağ koşullarında ortaya çıkar. Toxiproxy gibi araçlarla ağ emülasyonu, dağıtımdan önce bu tür sorunların belirlenmesine yardımcı olur.
İlk neden — veri eksikliği. Geliştirici test fikstürleriyle çalışırken prodüksiyonda beklenmedik değerlere sahip milyonlarca kayıt vardır. Geliştiricinin zorunlu olduğunu düşündüğü alanda NULL, bir isimde Unicode karakteri, çok uzun dize — bunların hepsi, sentetik verilerle yerel DB'de çoğaltılamayan hatalara neden olabilir.
İkinci neden — farklı derleme ve yapı bayrakları. Sürüm yapısı (Release/Distribution), hata ayıklama yapısından (Debug) farklı olabilir. Derleyici optimizasyonları, hata ayıklama günlüklerinin kaldırılması, işlev içine alma — bunların hepsi hataları gizleyebilir veya tam tersine ortaya çıkarabilir. Tipik örnek: hata ayıklama yapısında assert çalışırken sürümde değişken başlatma sırasının farklı olması nedeniyle başarısız olur.
Üçüncü neden — yerel önbellek ve geçici dosyalar. Geliştirici hatayı fark etmeyebilir çünkü tarayıcıda eski komut dosyaları önbelleğe alınmış, Redis'te güncel olmayan veriler kaydedilmiş ve dosya sisteminde önceki çalıştırmalardan geçici dosyalar bulunmaktadır. Temiz bir çalıştırma (gizli mod, önbelleği temizleme, fresh install) genellikle “kendiliğinden” ortaya çıkmayan hatayı çoğaltır.
Dördüncü neden — küresel ve yerel bağımlılık çatışmaları. Ruby gems, Python pip, Node.js npm gibi araçlar, kodun yerelde çalışmasına “yardımcı olan” ancak prodüksiyonda bulunmayan küresel olarak kurulu paketlere sahip olabilir. Sanal ortamlar (virtualenv, venv, nvm) kullanmak, projeyi küresel kurulumlardan ayırır ve ortamı tekrarlanabilir hale getirir.
“Benim makinemde çalışıyor” ifadesi ekipteki güveni yok eder. Bir geliştirici düzenli olarak hataları çoğaltamıyorsa, ekip arkadaşları onun yetkinliğinden veya test titizliğinden şüphelenmeye başlar. Zamanla bu, mikro yönetime yol açar: her değişiklik ikinci bir geliştirici tarafından doğrulama gerektirir ve bu da geliştirmeyi yavaşlatır. Google Project Aristotle'a göre, ekipteki psikolojik güvenlik üretkenliği doğrudan etkiler ve ortamla ilgili sürekli tartışmalar bunu azaltan faktörlerden biridir.
İkinci sorun — code review'in yavaşlaması. Bir geliştirici hatayı yerelde çoğaltamazsa, bir ekip arkadaşının pull request'ini “Bende çalışıyor — yani sorun sende” diyerek reddedebilir. Bu, çatışmalara yol açar ve özellik teslimatını geciktirir. Ortamın standartlaştırılması bu çatışmayı ortadan kaldırır: her iki geliştirici de aynı Docker konteynerinde çalışıyorsa, “kimde çalışıyor” sorusu anlamını yitirir.
Üçüncü sorun — takipçide hata kaybı. “Geliştiricide çoğaltılamayan” hatalar genellikle “Çoğaltılamıyor” (Cannot Reproduce) notuyla kapatılır. Bir ay sonra hata prodüksiyonda ortaya çıkar ve düzeltilmesi 10 kat daha pahalıya mal olur. Kural: hata en az bir kişide çoğaltılabiliyorsa — geliştiricide çalışsa da çalışmasa da vardır.
İlk ve en etkili yöntem — Docker. Tüm proje, ek işlemler olmadan docker-compose up ile çalışabilmelidir. Veritabanı, önbellek, mesaj kuyruğu, web sunucusu — her şey konteynerlerde çalışır. Geliştirici yalnızca Docker ve Git kurar. Gerisi — konteynerlerin içinde. Bu, işletim sisteminden bağımsız olarak tüm ekip üyelerinin aynı ortama sahip olmasını sağlar.
İkinci yöntem — sürüm yöneticileri. Docker mümkün değilse (lisans kısıtlamaları, eski altyapı), nvm (Node.js), rbenv (Ruby), pyenv (Python), sdkman (Java) kullanın. Sürüm yöneticileri, proje içinde dillerin ve araçların sürümlerini değiştirmeye olanak tanır. .nvmrc, .ruby-version, .python-version dosyaları depoda olmalı ve CI/CD tarafından kontrol edilmelidir.
Üçüncü yöntem — sanal makineler için Vagrant. Vagrant, VirtualBox veya VMware üzerinde belirtilen işletim sistemi ve yapılandırmayla sanal bir makine başlatır. VM içinde tüm bağımlılıklar, sağlama komut dosyaları (shell, Ansible, Puppet) aracılığıyla kurulur. Vagrant, Docker'dan daha ağırdır ancak işletim sistemi düzeyinde tam izolasyon sağlar — belirli bir Linux çekirdeği sürümüne bağımlı projeler için kullanışlıdır.
Dördüncüsü — makefile ve bootstrap komut dosyaları. install, test, build, clean hedeflerine sahip basit bir Makefile bile rutin görevleri standartlaştırabilir. make install komutu tüm bağımlılıkları kurmalı, DB'yi yapılandırmalı ve test verileri oluşturmalıdır. Tüm geliştiriciler için tek giriş noktası, ortam kurulumundaki manuel hataları ortadan kaldırır.
Ana araç — bağımlılıkların lock dosyaları. package-lock.json (npm), yarn.lock (Yarn), Podfile.lock (CocoaPods), pubspec.lock (Flutter) her paketin tam sürümünü sabitler. Lock dosyası olmadan, farklı zamanlarda bağımlılık kuran iki geliştirici farklı alt sürümler alabilir. Lock dosyası depoda olmalı ve manuel olarak düzenlenmemelidir.
İkinci araç — depoda .env.example. Yorumları olan ortam değişkenleri şablon dosyası. Geliştirici bunu .env'ye kopyalar ve kendi değerlerini girer. CI/CD hattı, tüm zorunlu değişkenlerin ayarlanıp ayarlanmadığını kontrol eder. GitLab 2023'e göre, .env.example kullanan ekipler, ortam değişkenleriyle ilgili olay sayısını %40 oranında azaltır.
Üçüncü araç — pre-commit kancaları. Her commit'ten önce çalışan otomatik kontrol: lint, biçimlendirici, tür denetimi, testler. Kancalar tüm geliştiricilerde aynı şekilde yapılandırılmışsa, “yerel makinede geçen” biçimlendirme veya tür hataları prodüksiyona ulaşmaz. JavaScript için Husky ve Python için pre-commit popüler çözümlerdir.
Dördüncüsü — temiz bir ortamda testleri çalıştıran CI/CD hattı. Testler CI'da geçiyor ancak yerelde geçmiyorsa — sorun yerel ortam kurulumundadır. Testler CI'da geçmezse — pull request birleştirilmez. Bu katı kural, “yerelde çalışan” hataların ana dala girmesini engeller.
Sıkça sorulan sorular
Bu bir savunma tepkisidir: geliştirici hata ayıklamaya çok zaman harcar ve kodun çalışmadığını duymak psikolojik olarak acı vericidir. İfade, suçluluk duymadan “geçiş yapmak” ve nedeni aramaya başlamak için zaman verir.
Temiz bir ortamda (clean install, gizli mod) hatayı çoğaltmasını isteyin. Çoğaltılmazsa — bağımlılık sürümlerini ve ortam değişkenlerini karşılaştırın. İşe yaramazsa — prodüksiyonla aynı Docker ortamını başlatın.
Docker, herhangi bir işletim sisteminde aynı şekilde çalışan, sabit yapılandırmaya sahip yalıtılmış bir konteyner sağlar. Tüm geliştiriciler aynı Dockerfile'ı kullanır, bu nedenle ortam aynıdır. Hata konteynerde çoğaltılamazsa — sorun sistemde değil, gerçekten kodda demektir.
Lock dosyası, tüm geçişli bağımlılıkların tam karma ve sürümlerini sabitler. Paket kaydında bağımlılığın yeni bir sürümü çıksa bile, lock dosyasından kurulum, her geliştiricinin diğerleriyle aynı paket setini almasını garanti eder.
VirtualBox ile Vagrant, proje işletim sistemi çekirdeğinin belirli modüllerine bağımlıysa veya çekirdek düzeyinde tam izolasyon gerekiyorsa uygundur. Projelerin %90'ı için Docker daha hafif, daha hızlı ve daha kullanışlıdır. Seçim, projenin işletim sistemiyle ne kadar derin etkileşime girdiğine bağlıdır.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun