Ana içeriğe geç

Source Control Standartları

· 16 dakikalık okuma
Ahmet Buğra Kösen
Software Developer

project-history 1.png

Ekipler büyüdükçe artık belirli standartları uygulamak zorunlu hale gelmeye başlar. Aksi takdirde projelerin veya kaynak kodların yönetimini sağlamak ve çalışma ortamının verimliliğini sürdürmek zorlaşır.

Birazdan anlatacağımız standartlar için gelebilecek; "neden yapıyoruz ki?" veya "o kadar işin arasında birde bu vakit kaybı değil mi?" gibi sorularının genel bir cevabını vererek giriş yaptığımıza göre, bu dökümanda neleri ele alacağımıza bir göz atalım;

  • Semantic Versioning
  • Perfect Commit;
    • Perfect Commit Messages
    • Conventional Commits
  • Branch Naming

Semantic Versioning(SemVer)

Source Control Standarts isimli bir dökümanda neden versioningle ilgili bir bölüm olduğunu merak ediyorsan sabırsızlanma ve okumaya devam et 😉

Semantic Versioning Nedir?

Semantic versioning (semantik sürümleme), yazılım projelerinde sürüm numaralarını belirlemenin standart bir yoludur. Bu standart, sürüm numaralarının anlamlı ve öngörülebilir olmasını sağlar. Semantic versioning, genellikle MAJOR.MINOR.PATCH formatında kullanılır.

Her bölümün ne anlama geldiğine ve ne zaman artırılacağına bir göz atalım:

  1. MAJOR (Ana Sürüm): Geriye dönük uyumluluğu bozan değişiklikler yapıldığında artırılır.
  2. MINOR (Alt Sürüm): Geriye dönük uyumlu yeni özellikler eklendiğinde artırılır.
  3. PATCH (Yama): Geriye dönük uyumlu hata düzeltmeleri yapıldığında artırılır.

Frame 1.png

Neden Semantic Versioning Kullanmalısın?

  • Anlaşılabilirlik: Sürüm numarasından yazılımın ne kadar değiştiğini ve bu değişikliklerin uyumluluk üzerinde ne tür etkileri olduğunu kolayca anlayabilirsin.
  • Güvenilirlik: Yazılımı kullananlar, sürüm numarasına bakarak güncellemelerin risklerini daha iyi değerlendirebilirler.
  • İşbirliği: Takım arkadaşların ve kullanıcılar, sürüm numaralarının anlamını bildiklerinde, projeye katkı sağlama ve yazılımı kullanma konusunda daha güvenli hissederler.

Semantic Versioning Nasıl Yapılır?

Ekip olarak bir e-ticaret sitesi için API geliştirdiğimizi varsayalım. Aşağıda buna göre senaryolar ve bu senaryolarda sürüm numarasının nasıl değişeceği yer almakta:

  • API temel özelliklerini ekliyorsun ve henüz kullanıcıların kullanımına uygun değil, yani stable bir sürüme sahip değilsin. Bu durumda ilk sürüm numarası 0.1.0 şeklinde olmalı.
  • Kullanıcıların giriş yapmaları için gerekli endpointleri ekledin. Bu durumda bir sonraki sürüm numarası 0.2.0 olmalı.
  • Yeni eklediğin endpointlerde hatalar olduğunu farkettin ve bunları düzelttiğin bir sürüm yayınladın. Bu durumda sürüm numarası 0.2.1 olmalı.
  • API'ın temel özelliklerini tamamladın ve artık api kullanıcıların kullanabileceği bir hale geldi. Bu durumda ilk sürüm numaran 1.0.0 olmalı.
    • Stable bir sürüm yayınlamak, aslında son deneme sürümünü teslim etmek anlamına da gelir. Yani 0.2.1 ve 1.0.0 sürüm numaraları aslında aynı olabilir. Bu durumda geriye dönük uyumsuzluk beklenmez. Geriye dönük uyumsuzluklar genellikle 1.0.0'dan sonraki sürümlerde ortaya çıkar.
  • API'ına belirli özellikler ekledin ve şuanki sürüm numarası 1.17.4. API performans iyileştirmesi ve güvenlik zafiyetlerini gidermek amacıyla kullandığın framework ve paketlerin versiyonunu güncelledin ve buna bağlı olarak API'da artık geriye dönük uyumluluğu olmayan değişiklikler yaptın. Bu durumda bir sonraki sürüm numaran 2.0.0 olmalı.
  • İş birimin senden yeni bir ödeme altyapısı eklemeni istedi. Bu durumda yeni sürüm numaran 2.1.0 olmalı

Hayali e-ticaret sitemizi semantik bir şekilde versiyonladığımıza göre SONUÇ ;

Semantic versioning, projeni düzenli ve anlaşılır tutmanın bir standardıdır. Projenin her aşamasında, sürüm numaralarını doğru bir şekilde güncelleyerek projenin kullanıcılarına ve ekip arkadaşlarımıza projenin durumu hakkında daha net bilgiler verebiliriz. Bu standardı kullanarak, yazılım geliştirme sürecini daha yönetilebilir ve güvenilir hale getirebiliriz.

Semantic Versioning uygularken uyman gereken kurallar ve daha fazla bilgi için SemVer Resmi Sitesi'ne göz atabilirsin.


Perfect Commit Messages

Untitled

Her ne kadar önemsiz görülse de, commit mesajları yazılım geliştirme sürecinin önemli bir parçasıdır. İyi yazılmış bir commit mesajı, hem senin hem de takım arkadaşlarının projeyi daha iyi anlamasını sağlar. "Bu değişikliği kim neden yaptı abi?" gibi soruların tarih olmasını sağlayıp, özellikle sürekli farklı projelerde görev alan ekip üyeleri için ciddi bir zaman kazancı sağlar.

Mükemmel commit mesajını yazmak işin sadece yarısı, mükemmel commitleri oluşturmak için değişiklikleri parçalara bölmek ve bu parçalara neler eklenmesi gerektiğini planlamak da aynı şekilde önemlidir. Commit'ler bir task'ı ele alış biçiminde önemli bir rol oynayabilir. Değişiklikleri mantıksal olarak commit'lere gruplandırmak, bir task'ı planlama ve onu daha küçük parçalara bölerek yazılım geliştirme sürecini iyileştirmen için de olanak sağlar.

Bu, task'ın ve bu task'a ürettiğin çözüm hakkında daha fazla düşünmeni sağlayacak, sadece işin başında değil, aynı zamanda değişiklikleri commitlere parçalayıp bölerken ve commit mesajını yazarken de bunu yapmanı sağlayacak. Bu da, uygulamanı yeniden gözden geçirmeni ve belki de gözden kaçan edge caseleri, eksik testleri veya unuttuğun başka herhangi bir şey fark etmeni sağlayabilir.

Peki Nasıl?

Yaptığımız geliştirmeyi uygun şekilde commitlere parçalama işini sana bıraktığımıza göre, commit mesajını nasıl yazmalıyız sorusuna cevap verelim. Burada tutarlılık çok önemlidir, bu yüzden ekipler önce aşağıdaki üç konuda tartışmalı ve anlaşmalıdır;

  • Style: Commit geçmişinin okunabilir olmasını sağlamak için önemli bir rol oynar. Dil bilgisi, noktalama işaretleri, büyük harf kullanımı ve satır uzunlukları gibi konuları içerir.
  • Content: Content'i standardize etmek kolay değildir. Fakat yapılan değişikliklerin neden yapıldığı ve nasıl gerçekleştirildiği hakkında bilgiler ve gerektiğinde, yapılan değişikliklerin teknik detaylarını ve etkilerini içermelidir.
  • Metadata: Issue Tracking Id'leri, değişikliklerin test edilip edilmediğini belirten notlar veya gerekirse, kod inceleme sürecinde elde edilen bulgular veya yorumlar gibi ek bilgiler içermelidir.

Bu üç konuyu ele almanın tek bir yolu yoktur yani tartışmaya açıktır, ancak çoğu Git commit mesajı belirli bir deseni takip eder. Aşağıda bu yaygın kullanılan deseni inceleyeceğiz.

Şablon

[subject]

[optional body]

[optional footer(s)]

Subject (Başlık)

Tıpkı bir e-postada olduğu gibi, başlık çok önemli bir parçadır. Genellikle insanların okuyacağı ilk, belki de tek kısım burasıdır, bu yüzden görsel olarak çekici ve kolay anlaşılabilir olmalıdır, gereksiz büyük harf ve noktalama işaretlerinden kaçınmalı ve doğru anahtar kelimeler kullanılmalıdır.

Emir kipi standarttır; Git, senin adına bir commit oluşturduğunda (örneğin git merge veya git revert komutlarını çalıştırdığında) emir kipini kullanır. Bu, "Added" veya "Adds" yerine "Add" yazman gerektiği anlamına gelir. Başlıkta yazan metin, şu cümleyi tamamlamalıdır: "Eğer uygulanırsa, bu commit...". Çoğu ekip commit subject için aşağıdaki kuralları uygular;

  • Büyük harfle başlamalı
  • Nokta ile bitmemeli
  • 50 karakter veya daha kısa olmalı
    • Karakter sınırı elbette aşılabilir, ancak commit mesajının net ve sade olmasını sağlamak için bunu uygulamak iyi bir pratik olacaktır.

İyi bir commit başlığına örnek : Update configuration files with new staging URL

Tekrar edelim, bu kurallar "bunu yapamazsın, yaparsan taş olursun" gibi katı kurallar değildir. İstersen commit mesajlarına emoji bile ekleyebilirsin 😊

Body (Gövde)

Başlık çoğu zaman kendini açıklayıcıdır, ancak bazen "body" alanına daha fazla bilgi eklemek gerekebilir. Bu alanı, NEYİN ve NEDEN değiştiği hakkında daha fazla bağlam sağlamak için kullanırız.

Çoğu ekip commit body için aşağıdaki kuralları uygular;

  • Subject'ten ayırmak için bir boş satır kullanılmalı
  • Paragraflar boş satırlarla veya bullet list vb. stillerle düzenlenmeli
  • Satır uzunluğu 72 karakter veya daha kısa olmalı

"Bi dakika arkadaşım bu karakter sınırı neden 72" dediğini duyar gibiyim;

72 karakter sınırı, terminal genişliği ve e-posta istemcileri gibi sistemlerin metin kaydırmayı(wrapping) düzgün şekilde işleyebilmesi ve okunabilirlik gibi tarihsel ve pratik nedenlerden dolayı yaygın olarak kullanılır. Bu, eski donanımlar ve yazılımlar için önemli bir gereklilikti ve artık bunu uygulamamıza pek ihtiyaç yok gibi gözüküyor. Fakat bu sınır, mesajların daha düzenli ve anlaşılır olmasını sağlamak için günümüzde de bir standart olarak kullanılabilir. Özellikle open-source projelerde hala yaygın kullanılan bir konvansiyondur.

Subject alanındaki karakter sınırlamasına benzer şekilde, bu daha çok bir yönerge olmakla birlikte, katı bir kural değildir. Bunu uygulayıp uygulamamak daha çok commit mesajlarını sıklıkla görüntülediğimiz editörün yeteneklerine bağlı olarak değişmelidir.

Tim Pope'un aşağıdaki örneği, stil olarak ulaşmamız gereken hedefin bir örneği olarak gösterilebilir:

Short(50 chars or less) summary of changes

More detailed explanatory text, if necessary. Wrap it to 72 characters.
The blank line separating the summary from the body is critical (unless
you omit the body entirely); tools like rebase can get confused if you run
the two together.

Further paragraphs come after blank lines. Bullet points are okay, too.

- Use a hyphen or asterisk for bullet points.
- Capitalize the first letter of each point.

Metadata/Footer

Footer'a commit'in ilgili olduğu Azure DevOps taskları veya user storyleri, Pull Request'ler veya Jira ticket'larını ekleyebiliriz. Aynı zamanda bu alan, kullanımdan kaldırılan özelliklerin ve geriye dönük uyumsuz değişikliklerin belirtilmesi gereken yerdir. Örnek;

BREAKING CHANGE: <summary>
<blank line>
Fixes #<user story>
Closes #<pr>

"Bir commit mesajına bu kadarda uğraşmak overengineering be!" diye hayıflanan ve takımın standartlarını yakalayamamış arkadaşım bir önceki atttığın commit mesajını düzeltebileceğin bir yöntemde sana gelsin. Git'in --amend seçeneğini kullanarak çirkince yazdığın o commit mesajını düzenleyip hatandan geri dönebilirsin 😑

Şablonu parça parça inceledik. Hepsini bir araya getirdiğimizde, commitlerimiz aşağıdaki örnekteki gibi olmalıdır;

Add user authentication feature

- Implemented user authentication using JWT tokens for secure login.
- Added user registration functionality with password hashing for security.

Fixes #123
Closes #456
Not Tested

Conventional Commits

Untitled

Gelelim fasulyenin faydalarına 🫘

Bir önceki sectionda oluşturduğumuz perfect commit'in üzerine, Conventional Commit şartnamesinde belirlenen standartları uygulayarak, commit mesajının üstünde bir çatı oluşturmaya çalışıyoruz ki anlamlı bir commit geçmişi elde edip, bu geçmişten çeşitli raporlar elde edebilelim ve bize belli başlı yetenekler kazandırsın. Yani human-readable olan commit mesajlarımızı human & machine-readable hale getireceğiz. Ayrıca bu şartname Semantic Versioning ile uyumludur.

Şablon

<type>[optional scope]: <subject>

[optional body]

[optional footer(s)]

Conventional Commit, aşağıdaki yapısal unsurları içermelidir;

  1. fix: fix tipi bir commit kodunuzdaki bir hatayı düzeltir (Semantik versiyonlamadaki PATCH ile paraleldir).
  2. feat: feat tipi commit kodunuza yeni bir özellik ekler (Semantik versiyonlamadaki MINOR ile paraleldir).
  3. BREAKING CHANGE: BREAKING CHANGE: ile başlayan bir footer ya da type/scope sonuna eklenmiş bir ! içeren commit geriye dönük uyumluluğu olmayan bir değişiklik getiriyordur (Semantik versiyonlamadaki MAJOR ile paraleldir). Bir BREAKING CHANGE harhangi bir tip commit içinde olabilir.

Yukarıdakiler Conventional Commit şartnamesinde yazılı olan ve semantic versioning'e paralel olan tiplerdir. Bunlar dışında yaygın kullanılan fakat semantic versioning'e etkisi olmayan tipler ve kodumuza olan etkileri;

  1. docs: Sadece dökümantasyon ile ilgili değişiklikler yapar. Örneğin, README dosyasını güncellemek.
  2. style: Kodun stilini ve formatını değiştirir, ancak işlevini etkilemez. Örneğin, boşlukları düzenlemek veya satır sonu karakterlerini değiştirmek.
  3. refactor: Kodun işlevselliğini değiştirmeden yeniden düzenler. Örneğin, bir fonksiyonu daha okunabilir hale getirmek.
  4. perf: Performans iyileştirmeleri yapar. Örneğin, bir algoritmayı daha hızlı çalışacak şekilde optimize etmek.
  5. test: Test ekler veya mevcut testlerde değişiklik yapar. Örneğin, bir fonksiyonun yeni bir test senaryosunu eklemek.
  6. build: Build sistemini veya dış bağımlılıkları etkileyen değişiklikler Örneğin (örnek: nuget, npm)
  7. ci: Sürekli entegrasyon (CI) yapılandırmasında değişiklikler yapar. Örneğin, CI araçlarının yapılandırma dosyalarını güncellemek.
  8. chore: Diğer kategorilere uymayan değişiklikler yapar. Örneğin, .gitignore dosyasına yeni exclusionlar eklemek.
  9. revert: Önceki bir commit'i geri alır. Örneğin, yanlışlıkla yapılan bir değişikliği geri almak.

Örnekler

Subject ve geriye dönük uyumluluğu olmayan bir değişiklik içeren footer'a sahip bir commit mesajı;

feat: allow provided config object to extend other configs

BREAKING CHANGE: `extends` key in config file is now used for extending other config files

Geriye dönük uyumluluğu olmayan bir değişikliği! ile belirten bir commit mesajı;

feat!: send an email to the customer when a product is shipped

Kapsamı(scope) belirten ve geriye dönük uyumluluğu olmayan bir değişikliği ! ile belirten bir commit mesajı;

feat(api)!: send an email to the customer when a product is shipped

! içeren ve BREAKING CHANGE içeren footer'a sahip commit mesajı;

chore!: drop support for Node 6

BREAKING CHANGE: use JavaScript features not available in Node 6.

Body'si olmayan commit mesajı;

docs: correct spelling of CHANGELOG

Scope belirtilen commit mesajı;

feat(lang): add Polish language

Çok paragraflı bir body ve birden çok footer metadatası içeren commit mesajı;

fix: prevent racing of requests

Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.

Remove timeouts which were used to mitigate the racing issue but are
obsolete now.

Reviewed-by: Z
Refs: #123

Şablonu ve içeriğini aklımızda canlandırabildiğimize göre dökümanı daha da uzatmamak adına, kalan detayları Conventional Commits Şartnamesi'nden okuyup öğrenmekte senin üzerine düşen görev. Şimdi diğer bölümlerde yaptığımız gibi neden kullanıyoruz sorusuna cevap verip bu bölümü sonlandıralım;

  • CHANGELOG'ları otomatik olarak human&machine-readable şekilde oluşturmamızı sağlar.

    Untitled

  • Kolayca semantik versiyonlama yapmamıza olanak sağlar.

  • Hangi projenin, hangi kapsamında, hangi türde geliştirmeler, iyileştirmeler, hata düzeltmeleri yapıldığı raporlanabilir.

  • Belirtilen tiplere göre otomasyon araçları geliştirmeye veya operasyonel işlemler yürütmeye (tip alanında "ci" görülen commit mesajlarının deployment pipeline'larını tetiklemesi, "test" görülenlerin birim veya uçtan uca testleri tetiklemesi vs. gibi.) imkânlar tanınıyor.

  • Kalabalık ekiplerde aynı dilin konuşulmasını, kişilerin, başkalarının yaptığı değişiklikleri çok daha kolay anlaması sağlanır.

  • Jira, Trello gibi proje yönetim araçlarındaki iş detaylarından spesifik olarak ilgili işin yaşam döngüsü izlenebilir.

  • İnsanların daha yapılandırılmış bir commit geçmişini kendi kendilerine keşfetmelerine imkan vererek projelerinize katkıda bulunmalarını kolaylaştırmak.

  • Çıktı olarak sürüm takibi tarihçeleri üzerine analiz yapabilecek araçlar için düzenli bir veri seti oluşturuyor.

Bonus


Branch Naming

Frame 2.png

Commitlerimizin bir standardı olur da commitlerimizi pushladığımız branchlerin olmaz mı? Tabi ki var!

Kod base'de yapacağımız değişiklikleri yapmadan önce hepimiz bir branch oluşturuyoruz. Bu branchlerin yönetimi bazı durumlarda zorlaşabiliyor. Bunun önüne geçmek için branchleri etkili bir şekilde adlandırmak ve organize etmek, geliştirme sürecinin verimliliğini artırabilir. Branch isimlendirme konusunda dikkate alınması gereken yaygın kullanılan standartları incelemeden önce Regular Branches adını verdiğimiz uzun ömürlü branchlere bir göz atalım;

Git'teki regular branches, devam eden çalışmaları organize etmek için stable ve structured bir yol sunan uzun ömürlü branchlerdir. Bu branchler, repoda kalıcı olarak bulunur ve isimlendirmeleri basittir. Bazı regular Git branchleri şunlardır;

  • Master (master/main) Branch: Git reposundaki varsayılan production branchidir ve sürekli olarak stable olması gerekir. Geliştiriciler, kod review ve testten sonra değişiklikleri master branchine birleştirebilirler. Projedeki tüm ekip arkadaşları, master branchini stable ve up-to-date tutmalıdır.
  • Development (dev) Branch: Ana geliştirme branchidir ve geliştiricilerin yeni özellikleri, hata düzeltmelerini ve diğer değişiklikleri entegre etmesi için bir merkez olarak hizmet eder. Ana amacı, geliştiricilerin değişiklikleri doğrudan master branchine uygulamaktan kaçınmalarını sağlamaktır. Geliştiriciler, değişiklikleri dev branchinde test eder, inceler ve daha sonra master branchine birleştirirler.
  • QA (QA/test) Branch: QA ve otomasyon testi için hazır olan tüm kodları içeren branchtir. Production ortamına herhangi bir değişiklik uygulamadan önce QA testleri gereklidir, böylece stable bir kod base oluşturulur.

Regular branchler şimdi bahsedeceğimiz isimlendirme kurallarını uygulamazlar.

Style

  • Küçük harf ve "-"(tire): Branch isimleri için küçük harfler ve kelimeleri ayırmak için tire kullanmalısın. Örneğin, feature/new-login veya bugfix/header-styling.
  • Alphanumeric karakterler: Yalnızca alphanumeric karakterler (a-z, 0–9) ve tireler kullanmalısın. Noktalama işaretleri, boşluklar, alt çizgiler veya alphanumeric olmayan herhangi bir karakter kullanımından kaçınmalısın.
  • Ardışık tireler: Ardışık tirelerden kaçınmalısın. Örneğin feature--new-login karışık ve okunması zor olabilir.
  • Tire ile bitiş: Branch adını tire ile bitirmemelisin. Örneğin, feature-new-login- şeklinde bir branch ismi oluşturmamalısın.
  • Açıklayıcı: Yaptığın isimlendirme açıklayıcı ve öz olmalıdır, ideal olarak branch'te yapılan çalışmayı yansıtmalıdır.

Branch Prefix

Branchlerin amaçlarını hızlıca tanımlamak için prefixlerin kullanılması, branch türlerinin tanımlanmasına yardımcı olur;

  • Feature Branch: Bu branchler yeni özellikler geliştirmek için kullanılır. Örneğin, feature/login-system
  • Bugfix Branch: Kod içindeki hataları düzeltmek için kullanılır. Örneğin, bugfix/header-styling
  • Hotfix Branch: Production ortamındaki kritik hataları düzeltmek için doğrudan production branchinden oluşturulur. Örneğin, hotfix/critical-security-issue
  • Release Branch: Yeni bir production sürümü için hazırlık yapmak için kullanılır. Örneğin, release/v1.0.1
  • Documentation Branch: Döküman yazmak, güncellemek veya düzeltmek için kullanılır. Örneğin, docs/api-endpoints
  • Experimental Branch: Bir release'in veya sprintin parçası olmayan yeni ve henüz test edilmemiş özellikler veya fikirler geliştirmek için kullanılır. Bu branchler, deneme amaçlıdır ve mevcut projede uygulanıp uygulanmayacağı belli değildir. Örneğin; experimental/new-order-process-algorithm
  • WIP Branch: Geliştiricilerin üzerinde çalıştıkları, ancak henüz tamamlanmamış olan özellikler veya görevler için kullanılır. Düzenli geliştirme sürecinin bir parçası olabilir veya olmayabilir. Genellikle belirli bir görev veya özellik üzerinde çalışırken kullanılır. Örneğin; wip/refactor-auth-system
  • Merging Branch: Merge conflict'leri ****çözmek veya iki farklı branch'i birleştirmek için geçici olarak kullanılır. Örneğin; merge/feature-login-with-dev

Bazı iş akışlarında, özellikle daha büyük ekiplerde, Jira, Azure DevOps gibi bir proje yönetimi aracından gelen ticket/task numarasını branch adına dahil etmek yaygındır. Bu, belirli bir ticket/task için yapılan işin takibini kolaylaştırır. Örneğin, iş biriminiz kullanıcıların google ile login olabilmesini istedi. Bu özelliği eklemek için "T-123" numaralı bir jira ticket'ı açıldı, bu durumda branch adı feature/T-123-new-login-system olabilir. Konuya örnek olması açısından örnek branch isimleri;

  • Bir hata düzeltmesi için elinizde bir jira var ise örnek branch adı bugfix/EMJ-1789-fix-header-styling olmalı
  • Yeni bir geliştirme için elinizde bir User Story var ise örnek branch adı feature/US-1288-new-login-system olmalı
  • Yeni bir geliştirme için elinizde bir Task var ise örnek branch adı feature/T-1289-new-login-system olmalı

Git'te branch isimlendirme kuralları, sistem tarafından zorunlu tutulmasa da, özellikle bir ekip içinde çalışırken temiz ve anlaşılır bir kod base sağlamak için önemlidir. Bu kurallara uyarak, branchlerin kolayca anlaşılabilir olmasını sağlayabiliriz.


Yukarıda anlatılanları uygulayıp uygulamamak veya iş yoğunluğundan dolayı buna ayıracak vaktimiz yok gibi cümlelerle bunları ertelemek sana kalmış. Fakat Robert C. Martin'in Clean Code kitabında atıfta bulunduğu LeBlanc yasasının "Sonra asladır. (Later equals never.)" varsayımını unutmamak gerekiyor 🙂


Kaynaklar;