10 puan yazan GN⁺ 4 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Yazılım tasarımı, derslerden çok gerçek projelerde sorumluluk alındığında ve sorunlar kişinin kendi işi hâline geldiğinde daha derin öğrenilir
  • Conway Yasası, yazılımın organizasyonun sosyal yapısını tekrar ettiği görüşüdür; bilimsel kod ile endüstriyel kod arasındaki fark da teşviklerden kaynaklanabilir
  • rust-analyzer; hızlı derleme, stable desteği, C bağımlılığının olmaması ve birkaç saniyelik testleriyle yüksek verimli katkıcıların odaklanmasını kolaylaştırır
  • rust-analyzer, bağımsız özellikleri catch_unwind ile korudu ve PR çıtasını düşürdü; ancak çekirdek spine için çok daha katı kalite standartları uyguladı
  • Deneysel yapı uzun vadeli gerçeğe dönüşebilir; rust-analyzer da LSP mimarisi için bir prototip olarak başlayıp bir derleyiciyi daha sürdürme sonucuna vardı

Yazılım tasarımı en iyi sahada öğrenilir

  • Yazılım tasarımı, resmî derslerden ziyade gerçek projelerde sorumluluk alıp sorunları doğrudan çözerken daha iyi öğrenilir
  • Üniversitedeki tasarım dersleri ve ders projelerinde “mimar” rolünü üstlenmekten çok, ikinci gerçek proje olan IntelliJ Rust üzerinde tasarım sorunları kişinin kendi meselesi hâline gelince öğrenme asıl o zaman başladı
  • IntelliJ Rust’ta bazı hatalar yapıldı, ancak bunlar ölümcül değildi ve süreç içinde çok şey öğrenilebildi
  • Yazılım mühendisliği, meraklı birinin ilkeler üzerinden düşünüp çeşitli yazılar okuyarak öğrenebileceği kadar basit yönler de taşır

Teşvik yapıları ve Conway Yasası

  • Conway Yasası, yazılımın onu üreten organizasyonun sosyal yapısını tekrar ettiği görüşüdür
  • Endüstriyel yazılım ile bilimsel kod arasındaki fark, yazılım inşa etme bilgisinin kendisinden çok, insanları yazılım üretmeye yönelten teşvik yapılarından kaynaklanıyor olabilir
  • “3 ay içinde makale yayımlamak zorunda olan bir doktora öğrencisi” gibi durumlar, bilimsel kodun biçimini belirleyen önemli etkenlerden biri olabilir
  • Teşvik yapılarına genel olarak iki şekilde karşılık verilebilir
  • Projenin teşviklerini tasarlamak veya yönlendirmek

    • Bir projenin teşvik yapısını tasarlama ya da ayarlama fırsatı nadirdir, ama böyle bir fırsat doğduğunda etkisi büyüktür
    • TIGER_STYLE içindeki asıl nokta, kural listesinin kendisinden çok bu kuralları iyi tercihlere dönüştüren sosyal bağlamdır
  • Değiştiremiyorsan kısıtlara uyum sağlamak

    • Teşvik yapıları neredeyse hiçbir zaman istenildiği gibi verilmez; değiştirilemiyorsa o yapıya uyum sağlamak gerekir
    • Endüstriyel yazılım projelerinde de “doğru yapmaya yetecek zaman” neredeyse hiç olmaz; eldeki kısıtlar içinde mümkün olanın en iyisini yapmak gerekir

rust-analyzer'da yapıyı katılımcılarla eşleştirme biçimi

  • rust-analyzer, hem derinliği hem genişliği olan bir projedir
  • Derin tarafta, bir derleyici olmasının da etkisiyle olağanüstü ve adanmış katkıcıları çekebilir
  • Geniş tarafta ise klasik IDE’lerin amaca özel çok sayıda özelliği bulunduğundan, Rust öğrenenler ya da düzenli katkı sunması zor olan hafta sonu katkıcıları için kendi sıkıntılarını gidermek adına bir iki saat harcamaya uygundur
  • rust-analyzer’ın rustc derlemesi gerektirmemesi, stable üzerinde derlenmesi, C bağımlılığı taşımaması ve tüm test paketinin birkaç saniye içinde tamamlanması konusundaki ısrarının nedeni, yüksek verimli katkıcıları çekmekti
  • Derleme sistemi, insanların başka şeylerle uğraşmak zorunda kalmadan borrow checker üzerinde çalışmaya odaklanabilmesi için cilalandı
  • Hafta sonu katkıcılarını çekmek için rust-analyzer’ın iç yapısı birçok bağımsız özelliğe bölündü ve her özellik çalışma anında catch_unwind ile korundu
  • Özellik PR’leri için çıta, “happy path çalışıyor ve testi var” seviyesine indirildi; ilgili kod çökse bile bunun kabul edilebilir olduğu düşünüldü
  • Ancak iki koşul vardı
    • Kalite sorunları tekil özelliğin içinde izole kalmalı ve diğer bölümlere yayılmamalıydı
    • Çalışma anındaki çökmeler kullanıcıya görünmemeliydi; bunun için rust-analyzer özellikleri değişmez snapshot’lar üzerinde çalışmalı ve veriyi kirletememeliydi
  • Buna karşılık, özellikleri taşıyan çekirdek spine için çok daha katı kalite standartları uygulandı

Deneysel yapının uzun vadeli gerçeğe dönüşme riski

  • Teşvik yapısını düzeltmek yerine ona uyum sağlandığında, geleceğin belirsiz olduğu ve çoğu zaman en rahatsız edici biçimde gerçeğe dönüşebileceği akılda tutulmalıdır
  • rust-analyzer’ın başlangıçtaki motivasyonu, IntelliJ Rust içinde ikinci bir paralel derleyici yazmaktan kaçınmak ve LSP için daha iyi bir mimariyi bir prototip üzerinden doğrulayarak elde edilen dersleri rustcye geri taşımaktı
  • Bu yüzden çekirdek kısmı da dahil olmak üzere kod son derece deneyseldi
  • Sonuçta bir derleyiciyi daha sürdürmek zorunda kalındı
  • Benzer şekilde uutils projesi de Rust öğrenenler için başlıca hedeflerden biri olarak başlayıp Ubuntu’nun coreutils uygulaması hâline geldi

Faydalı kaynaklar ve kitaplar

  • Tüm doğru cevapları içeren tek bir kitap yok; pratik deneyim vazgeçilmez görünüyor
  • Boundaries by Gary Bernhardt
    • Somut tavsiyeleri çok sağlam ve daha üst düzey araştırmaları tetikleyen bir kaynak
  • How to Test
    • Testin önemini hemen anlamıştım; ancak yaygın biçimde alıntılanan birçok test tavsiyesinin pratikte işe yaramadığını kabul etmek ve gerçekten çalışan yaklaşımı kavramsallaştırmak uzun zaman aldı
  • ∅MQ guide ve Pieter Hintjens’in yazıları
    • Conway Yasası tarzı düşünceyle tanışmamı sağlayan kaynaklardı
    • rust-analyzer’ın özellik geliştirme mimarisi, optimistic merging yaklaşımının uygulanmış bir biçimidir
  • Reflections on a decade of coding by Jamii
  • Ted Kaminski blogu
    • Var olmayan bir kitap için notlar biçiminde yazılmış; yazılım geliştirme üzerine tutarlı bir kurama en çok yaklaşan kaynaklardan biri
  • Software Engineering at Google ve Ousterhout’un The Philosophy of Software Design kitabı

1 yorum

 
GN⁺ 4 시간 전
Hacker News yorumları
  • Bunu bir kopya kağıdı olarak özetlersek, iyi tasarım tek bir fikrin bütüne nüfuz ettiği ve sürprizi en aza indirdiği bir yönde olmalı
    Sistem izin veriyorsa insanlar eninde sonunda onu öyle kullanır ve “herkes sadece X yapsa” diye başlayan çözümler çözüm değildir
    Veriyi dönüştüren kısımla kullanan kısmı ayırın; veri modeli koddan daha uzun ömürlüdür ve bağlılık birçok sorunun köküdür
    Versiyonlama kaçınılmazdır, durum açıkça görünür olmalıdır ve her bilgi için tek bir doğruluk kaynağı bulunmalıdır
    İsimlendirmeye daha fazla zaman ayırın; test etmesi zorsa tasarım yanlıştır ve belgelenmemiş kararlar sonradan pişmanlık yaratır
    İletişim bir maliyettir, bu yüzden ödemeden önce gerekçelendirilmelidir; mühendisin işi ise eksik bilgi içinde sezgisel kurallarla problem çözmektir

    • Bunun büyük kısmı aslında yazılım mimarisiyle çok da ilgili görünmüyor; belki sadece “sistemin bazı parçalarını yalıtın” kısmı buna girer
      Yazının kendisi de yazılım mimarisi bakış açısından pek tutarlı değildi
      4+1 architecture view, UML kısmını çıkarırsak, büyük resmi düşünmek için iyi bir kavramsal çerçeve; Pattern-Oriented Software Architecture serisi de insanların vardığı çeşitli mimarileri iyi sınıflandırıyor
      Grady Booch bir dönem software architecture handbook hazırlıyordu ama şimdi neredeyse durmuş gibi; o zamanlar mailing listede şirketlerin ya da büyük açık kaynak projelerinin büyük sistem mimarileri belgelenirdi
      Bu tür kaynaklara inerseniz, mimarilerin ölçek, güvenlik, performans, birlikte çalışabilirlik, fail-safe gibi farklı odaklara göre şekillendiğini ve her hedefin gerçekçi trade-off'ları olduğunu görebilirsiniz
    • Hepsine katılmıyorum ama birkaç şey eklemek gerekirse, yazılımın birincil amacı önündeki problemi çözmek, ikincil amacı ise muhtemel gelecek problemlerini mümkün olan en az çabayla çözmektir
      Bu ölçüte göre daha iyiyse, kötü tasarım gibi görünse bile aslında iyi tasarımdır
      Arayüzler doğru kullanımı kolay, yanlış kullanımı zor hale getirmeli ve projeyi bilmeyen birinin nasıl kullanacağını düşünmelisiniz
      Doğru kod yazması kolay olmalı, şüpheli kod göze çarpmalı ve bug'lar olabildiğince sola kaydırılmalıdır
      Tek bir bug'ı düzeltmektense bir bug sınıfını ortadan kaldırmak daha iyidir; arayüzü değiştirmek implementasyondan daha zordur, bu yüzden arayüz doğruysa çirkin bir implementasyon da kabul edilebilir
      Yorumlar ve dokümantasyon kodun neden o biçimde olduğunu açıklamalıdır; dışarıdan daha basit görünen bir yöntem varsa ama bazı kısıtlar yüzünden kullanılamıyorsa bu da yazılmalıdır
      Veri açısından bakıldığında tekrar etmemek gerekir; aynı gerçeği birden fazla yerde saklarsanız sonunda tutarsızlık ve bug oluşur
      İyi döşenmiş yoldan çıkmanın bir maliyeti vardır; gerçekten değerliyse yapılabilir ama bu maliyet küçümsenmemelidir
      Daha kötü görünen sıkıcı teknoloji çoğu zaman daha iyi teknolojidir ve beklenen değeri “buna değer mi?” diye değil, “başka bir şey yapmakla kıyaslandığında buna değer mi?” diye hesaplamak gerekir
      Kendinizi başkalarından daha zeki sansanız bile sadece zeka ile çözülemeyen problemler vardır; bazı problemler de gerçekten patlamadan fark edilemez, bu yüzden başkalarının hatalarından öğrenmek gerekir
      Sürtünme sessiz bir katildir
    • Veri migrasyonu kaçınılmazdır, bu yüzden önceden planlanmalıdır; bu, versiyonlamanın doğal sonucudur
      Plan yapmak güzeldir ama bazen bizzat denemek gerekir ve her şeyin bir maliyeti vardır
      Maliyeti hesaba katmadan tasarlarsanız sonra zor seçimlere mecbur kalırsınız
    • Bir yılı aşkın süredir video oyunu yaparken sürdürülebilir bir engine ve ondan ayrılmış bir veri pipeline'ı, tamamen ayrılmış resource rendering/management katmanı ve açık durum geçişleri kuruyorum; bu listenin büyük kısmı birebir geçerli
      Tek başına yapılan bir projede bile engine'in kısıtları, “test sırasında ortaya çıkan garip yeni özelliği bu şekilde ekle” diyen bir rehber oluyor
      Böylece “belirli bir durum geçişinde birkaç kez çalışan yeni bir ses efekti yapmak için şunu yap” gibi devasa bir belgeyi sürekli elde tutmak gerekmiyor
    • Bu alanda çalışıyorum ve bizim tarafta tıp sektörünü modelliyoruz; bu yüzden başka alanlara göre daha az soyut
      Sistem tasarımcısı sektörü anlamalıdır; onun terminolojisini ve modelleme alışkanlıklarını tamamen benimsemek zorunda değildir ama veri setlerine neden ve hangi açıdan baktıklarını anlamalıdır
      Sağlık pazarının karmaşıklığını kasıtlı olarak basitleştirip gereksiz aşırı tanımlamaları kaldırarak daha birleşik bir model sunduğumuz yerler oldu ama bu tür değişiklikleri ancak problem alanını yeterince anladığımız için güvenle yapabildik
      Özellikle isimler neredeyse hiç ölmez
      Bazen ölürler ama bir adı değiştirmek aşırı çaba gerektirir; bu yüzden alan uzmanlarının isim önerilerini uzun süre gözden geçirip eksik bir şey olup olmadığını kontrol etmesine zaman ayırmak gerçekten çok değerlidir
      Bazı kavramlar dayatılabilir ama satış/pazarlama gibi iş tarafı sürekli sektör terminolojisini talep eder ve modelin mevcut sektör bakışına uymasını ister
      Bu akışı kırmaya karar verdiyseniz, o kopuşun niyeti ve amacı çok net olmalıdır
      Yazılımda en çok vurgulanması gereken özellik bakım yapılabilirliktir
      Yapmanın ne kadara mal olduğu önemlidir ama çalıştırmanın maliyeti; sadece altyapıyı değil biriken özellik isteklerini, kod refactoring'ini ve üçüncü taraf yazılım sürümlerini güncel tutmayı da kapsadığı için çok daha büyük etki yaratır
  • Öneri listesi, Ousterhout'un A Philosophy of Software Design kitabında olduğu gibi, çoğu zaman iyidir ama genel olarak yazılım mimarisinin kendisinden çok genel yazılım geliştirme ilkelerine yakındır
    Mimariyi görmek istiyorsanız Shaw/Garlan'ın Software Architecture: Perspectives on an Emerging Discipline gibi klasiklerini ve Mary Shaw'ın yazılarını tavsiye ederim
    Yazılım mimarisi alanının neden beklendiği gibi ilerlemediğini inceleyen Myths and Mythconceptions: What Does It Mean to Be a Programming Language, Anyhow? ya da Revisiting Abstractions for Software Architecture and Tools to Support Them gibi daha yeni makaleler de iyidir
    Pratik tarafta Unix pipe and filter ile REST'in neden başarılı olduğunu ve nerede, neden çöktüğünü incelemek faydalı olur; hexagonal architecture da temeldir
    Kişisel olarak, yazılım mimarisiyle metaobject protocol'ü ilişkilendirip bunu programlama dilleri ve programlama için yeni bir temel olarak görmeye çalışan Beyond Procedure Calls as Component Glue: Connectors Deserve Metaclass Status da var
    Bu, Mary Shaw'ın Procedure Calls Are the Assembly Language of Software Interconnection: Connectors Deserve First-Class Status metnine bir yanıt; eğer prosedür çağrıları assembly diliyse, yüksek seviyeli dil nasıl görünürdü diye soruyor
    Belki de yazılım mimarisinin daha parlak ve daha pratik bir geleceği vardır

    • Hexagonal architecture nedir?
    • “Eğer prosedür çağrıları assembly diliyse, yüksek seviyeli dil nasıl görünürdü?” sorusu, programlama dili teorisi ve yazılım mühendisliği araçlarına çok hakim değilim ama lambda calculus, LISP, APL, Clojure, TCL gibi şeylerin temel fikri değil mi diye düşündürüyor
      Birkaç veri yapısı ve tip ile küçük bir temel fonksiyon kümeniz varsa, bunları birleştirirsiniz
      Lisp'te sevdiğim şeylerden biri, daha karmaşık tiplerin, özellikle FFI'den gelenlerin, her zaman opak olmasıdır
      C benzeri bir dilde struct tanımladığınızda standart bir fonksiyon kümesi veren bir CLOS implementasyonu görmek isterdim
  • Mimari öğrenmenin en iyi yolu yeterince büyük bir proje yapmak değil, onu bakımını yapmaktır
    Üstelik bunu en az iki ya da üç projede yapmanız gerekir
    Proje çok küçükse her mimari işe yarar ve “büyük” projeyi satır sayısından çok, üzerinde çalışmış insan sayısı, daha iyisi takım sayısı belirler
    Karşılaştırma yapabilmek için en az iki farklı proje gerekir; ayrıca onlarca yıl tek bir projeye gömülüp modern problem çözme yollarını bilmeyen insanlar da gördüm
    Ama pratikte projeyi yapan kişinin terfi edip mimar olması, bakımını yapan kişinin olması ise daha nadirdir
    Google'da bu özellikle belirgindir; terfi için yeni bir şey çıkarmak gerekir, bakım yapmak terfi getirmez ve mümkünse lansmandan hemen sonra projeden çıkmak daha avantajlıdır
    Paradoksal biçimde, mimar olmaya en uygun kişi, şirket içinde kimsenin almak istemediği mevcut projelerin bakımına verilen dış yüklenici olabilir
    Bunlar mimariyi sürdürmek zorundadır ve birçok proje gördükleri için karşılaştırma yapabilirler
    Tabii saatlik ücret alıyorlarsa daha fazla fatura kesmek için mimariyi gereksiz yere karmaşıklaştırma riski de vardır

  • Bu bağlamda Architecture of Open Source Applications'ı gerçekten tavsiye ederim
    Her bölümün ilgili projenin bakımcısı tarafından yazıldığı bir kitap serisi, dolayısıyla mimariyi örnekler üzerinden öğrenebiliyorsunuz
    Sadece mimarinin ne olduğunu değil, onu doğuran kısıtları, çoğu zaman tarihi ve değişen proje vizyonunu da görüyorsunuz
    Çok yazarlı kitapların sınırlamaları yüzünden tüm bölümler aynı derecede iyi ya da ilginç değil ve hepsi eski, ama yine de okunmaya değer
    http://aosabook.org/

  • Üzerinde çalıştığım projenin zihinsel modelini daha iyi kurmaya zaman ayırmak istiyorum ama programlama dili ya da belirli mimari seçimler yüzünden, hatta zaman ayırmaya değmeyecek kadar karmaşıklaşmış kısımlar nedeniyle nefret etmeye başlayınca motivasyonum çok düşüyor
    Projeye göre değişir ama “full-stack developer” olarak çalışmak programlamanın keyfini kaçırıyor gibi geliyor
    Zaten haftada 40 saatimi hayal edilebilecek en sıkıcı projeye bakarak geçiriyorum

    • Bence bırak gitsin
      Kusursuz proje diye bir şey yok
      Ve eğer programlama dili bu kadar büyük bir sorunsa, gemiyi değiştirmek daha iyi olabilir
      Herkesin birden fazla dille çalışabilmesi gerektiğini düşünüyorum ama nihayetinde seçim senin
    • Analysis paralysis her zaman bir risktir; bu yüzden hedef mükemmellik değil esneklik olmalıdır
      En çok zaman harcadığınız kararın gerçekten en önemli karar olduğundan emin olmalısınız
      İyi tasarlanmış veri yapıları; framework, dil ve platformdan çok performans ve bakım yapılabilirlik üzerinde daha büyük etkiye sahiptir
      Kişisel olarak her gün ADHD ile çalıştığım için ilerlemeyi sürdürmek adına kendimi itmem gerekiyor; bu da önemsiz kararları seçip kapatarak dikkatli düşünme gerektiren kalan problem alanına odaklanmak anlamına geliyor
    • Bu daha çok duygu düzenlemesini geliştirmeniz gereken bir durum gibi geliyor
  • “Clean code” ya da “beautiful code” gibi ifadeler, junior'ların yazılım mimarisi iyi uygulamalarını öğrenmesine pek yardımcı olmuyor
    Bir junior neden ORM kullandığımızı sorduğunda senior'ın “daha temiz olduğu için” diye cevap vermesi geride sadece bir soru işareti bırakır
    Bunun yerine net bir hedef listesi tanımlamak daha iyidir
    Bakım yapılabilirlik, performans ve ölçeklenebilirlik, verimlilik, dayanıklılık, gözlemlenebilirlik, test edilebilirlik ve test edilmiş olma, güvenlik, yeni bir geliştiricinin kolayca okuyabilmesi gibi ölçütler birbirini dengeler
    Ölçüt sayısı arttıkça kararsız kalındığında daha iyi seçim yapmak kolaylaşır ve bunlar geliştirme ekibi dışındakiler için de anlamlıdır; böylece müşteriyle neye para ödendiği konusunda uzlaşılabilir
    Projeler ömrünün çoğunu bakım modunda geçirir; bu da projenin başarılı olduğu anlamına gelen iyi bir şeydir, dolayısıyla bakım yapılabilirlik de tanımlanabilir
    Yeni bir özelliği mimariyi bozmadan, hatta mümkünse tek bir method signature'ı bile bozmadan ekleyebilme yeteneği iyi bir başlangıç noktasıdır
    Soyutlamalara karşı çok dikkatli olmak gerekir
    “Soyutlamalar çoğu zaman istediğiniz şeyin ne kadar basit olduğunu gizler” sözü doğrudur ve ORM bunun tam örneğidir
    Çoğu durumda veriye birinci sınıf vatandaş gibi davranılmalıdır; bu, DBA'lerle işbirliğini de iyileştirir
    Erken optimizasyona düşmeden mutlu yolun dışını da düşünmek zordur ama bu, artı ve eksilere bakmadan iyi fikir gibi görünen bir implementasyona dalmayı önler
    Kodumu ele alacak kişinin çok kötü bir gün geçirdiğini hayal etmek, onu okumayı keyifli hale getirmeme yardımcı oluyor
    Oraya buraya bırakılmış yorumlar, çıkarılabilecek olsa bile bırakılan yerel değişkenler ve değişken adları buna dahildir
    Framework'ler dikkatle seçilmelidir; iyi bir hizmetkâr ama kötü bir efendidir
    “Frameworker değil, engineer olun” sözü burada yerini buluyor

    • Modüler framework'ler, “güçlü görüşleri olan framework”lerle kıyaslandığında altın değerindedir
      Güçlü görüşlerin kendisine karşı değilim; hatta library ya da tool için mükemmel bir özellik olabilir
      Böyle bir library ya da tool, kendi alanında muhtemelen ciddi bir alan uzmanlığı taşıyordur
      Ama framework dünyasında güçlü görüşler kolayca “elinde çekiç varsa her şey çivi görünür” noktasına kayabilir
      Her zaman böyle olmaz ve her zaman sorun da yaratmaz ama bir alanın ortodoksisini geniş ölçekte uyguladığınızda, onun gerekçesi alanın kendisine özgüyken daha geniş bağlamda kırılma riski doğar
      Bu yüzden başka ORM'lerin ya da persistence integration katmanlarının takılabildiği, router, validator ve probleme uymayan başka bileşenlerin değiştirilebildiği modüler framework'leri tercih ederim
      Framework ekosistemi içinde birden fazla paradigma için alternatifler varsa bu özellikle değerlidir
      Çünkü o zaman neredeyse uyan hazır bir aracı bulup kusurları net olan ve gerektiğinde telafi edilebilen bir seçeneği alabilirsiniz
      Bakım yapılabilirlik için NIH sendromuyla mücadele etmek çok önemlidir
      Bu, kaynakları şaşırtıcı bir hızla tüketen sürekli bir risktir
      Aynı zamanda bazı bileşenler doğrudan ayarlandığında ya da tamamen sahiplenildiğinde büyük fayda sağlayabilir; zor olan da hangisinin hangi tarafta kaldığını doğru değerlendirmektir
      Bakım yapılabilirliği listenin başına koymana güçlü biçimde katılıyorum
  • Sistem mühendisliği açısından bakıldığında, yazılım mimarisi tesisat tasarımı gibidir
    Çok önemlidir ama insanlar tesisatın içinde değil, tesisatı olan evde yaşar
    Tesisat, evin geri kalanını hesaba katmazsa sonradan düzeltmesi çok pahalı olabilir

  • Güzel yazı
    “Yazılım mimarisini öğrenmek”, tek bir doğru cevap olmadığını anlamaktır
    Bu hem sanattır hem bilim
    Okuma önerisi olarak Simplify IT - The art and science towards simpler IT solution'ı tavsiye ederim
    https://nocomplexity.com/documents/reports/SimplifyIT.pdf

  • Gary Bernhardt konuşmaları gerçekten özeldir
    İçlerinde başka ilginç yerlere götürebilecek çok sayıda fikir var