SOLID, nesne yönelimli kod için beş tasarım ilkesinin baş harflerinden oluşur: Single Responsibility (Tek Sorumluluk), Open/Closed (Açık/Kapalı), Liskov Substitution (Liskov Yerine Geçme), Interface Segregation (Arayüz Ayrımı) ve Dependency Inversion (Bağımlılıkların Tersine Çevrilmesi). Her ilke, kodu değiştirmeyi zorlaştıran ayrı bir soruna karşılık gelir. Beşinin ortak bir hedefi var: sistemin bir parçasını değiştirirken onunla ilgisi olmayan parçaları bozmamak. İlkeleri tasarım yaparken sorulacak sorular olarak kullanmak en doğrusu; her yere refleksle uygulandıklarında, önlemek istedikleri aşırı soyut kodu üretirler.
Beş ilke tek bakışta
İlke | Tek cümlede kural | Bozulduğunda görülen belirti |
|---|---|---|
Tek Sorumluluk (SRP) | Bir modülün değişmek için tek bir nedeni olur: değişiklik isteyen tek bir kişi ya da ekip | Bir ekip için yapılan değişiklik, başka bir ekibin kullandığı özelliği bozuyor |
Açık/Kapalı (OCP) | Yeni davranış yeni kodla gelir; test edilmiş koda dokunulmaz | Her yeni seçenek aynı |
Liskov Yerine Geçme (LSP) | Bir arayüzün her uygulaması o arayüzün sözleşmesine uyar | Çağıran kod elindeki nesnenin hangi sınıftan geldiğini kontrol ediyor ya da biri için özel |
Arayüz Ayrımı (ISP) | İstemciler kullanmadıkları metotlara bağımlı olmaz | Sınıflar "desteklenmiyor" diye hata fırlatan metotlarla dolu |
Bağımlılıkların Tersine Çevrilmesi (DIP) | İş kuralları kendi tanımladığı soyutlamalara bağlıdır; veritabanı ve dış servisler bu soyutlamaları uygular | Bir kuralı test etmek için veritabanı, HTTP sunucusu ya da API anahtarı gerekiyor |
Yazı boyunca kullanacağımız örnek
SOLID anlatımlarının çoğu aynı üç örneği tekrarlar: Dikdortgen'den türeyen Kare, yemekYe() metodu olan bir Isci arayüzü ve her işi tek başına yapan bir KullaniciServisi. Biz yazı boyunca tek bir problem üzerinden gideceğiz. Problem, Kasım 2023'ten beri yazılım ekibi olarak çalıştığımız müşterimiz Oplist için yaptığımız işten geliyor. Oplist, hastanelerin vardiya çizelgelerini otomatik hazırlayan bir SaaS platformu.
Çizelge hazırlayan bir sistem, kimin izinli olduğunu bilmek zorunda. Oplist, Türkiye'deki özel hastanelerin İK ve bordro sistemlerinden izin kayıtlarını canlı API entegrasyonlarıyla alıyor: hastalık izni (rapor) ve diğer izin nedenleri, saatlik izinler, eğitim gibi blok izinler. İşi zorlaştıran iki ayrıntı var. Birincisi, hastanelerin kaynak sistemlerinde aynı personele ait mükerrer kayıtlar olabiliyor ve bazı izin türleri diğerlerinin önüne geçiyor. Bu yüzden kaynağa güvenmiyoruz: tekilleştirmeyi Oplist'in kendi içinde yapıyor, öncelik sırasını da orada uyguluyoruz. İkincisi, çizelge kuralları izin türüne bağlı: saatlik izin kişiyi bütün gün çizelge dışında bırakmaz, eğitim bloğu bırakır. "Müsait değil" bilgisi yetmiyor, izin türünün de gelmesi gerekiyor.
Aşağıdaki kod bu yazı için hazırlanmış, sadeleştirilmiş bir TypeScript örneği. "Taslak" diye işaretlenen bloklar ayrıntıları atlıyor; geri kalanı strict açıkken tip kontrolünden geçiyor. Hiçbiri Oplist'in kodu değil.
Tek Sorumluluk: değişmek için tek bir neden
Robert C. Martin'in tanımı kolayca "bir sınıf tek bir iş yapmalı" diye yanlış anlaşılıyor. Martin 2014'te yazdığı açıklamada ilkenin aslında insanlarla ilgili olduğunu söylüyor: bir modül tek bir paydaş grubuna hizmet etmeli ki bir grubun istediği değişiklik, başka bir grubun dayandığı şeyi bozamasın.
Aşağıdaki sınıf aynı anda üç gruba hizmet ediyor:
// Taslak: tek sınıf, değişmek için üç ayrı neden
class LeaveImporter {
async run(hospitalId: string, from: Date, to: Date): Promise<void> {
const rows = await this.hrClient.get(`/leaves?from=${iso(from)}&to=${iso(to)}`); // İK yazılımının formatı
const leaves = rows.map(toLeave);
const resolved = dedupeAndApplyPrecedence(leaves); // çizelge kuralları
await this.db.leaves.upsertMany(hospitalId, resolved); // kayıt
await this.mailer.send(this.managerOf(hospitalId), summarize(resolved)); // bildirim
}
}İK yazılımının API'si değişiyor: biri run metodunu düzenliyor. Hastane eğitimin yıllık izinden önce gelmesine karar veriyor: yine run. Operasyon ekibi günlük özeti e-posta yerine Slack'te görmek istiyor: yine run. Her düzenleme diğer ikisini riske atıyor. Öncelik kuralını test etmek için de HTTP istemcisinin, veritabanının ve e-posta servisinin sahtesini (mock) kurmanız gerekiyor.
Sınıfı bu üç sorumluluğa göre bölün:
// Taslak
interface LeaveSource {
fetchLeaves(from: Date, to: Date): Promise<Leave[]>; // tek bir İK sistemiyle konuşur
}
// Saf fonksiyon: tekilleştirir, kişi ve gün başına önceliği yüksek olan izni tutar (gövdesi bir sonraki bölümde)
function resolveLeaves(raw: Leave[]): Leave[];
async function importLeaves(source: LeaveSource, store: LeaveStore, notify: Notify, from: Date, to: Date) {
const leaves = resolveLeaves(await source.fetchLeaves(from, to));
await store.save(leaves);
await notify(summarize(leaves));
}En değerli parça resolveLeaves. Dizi alıp dizi döndürdüğü için en önemli kurallar (hangi kayıt mükerrer, hangi izin önce gelir) mock kurmadan, düz veriyle test edilebiliyor. Bu tür kurallar etraflarındaki teknolojiden daha uzun ömürlü olur. Kullanıcı arayüzü ve API yeni bir framework'e taşındığında, kural modülü testleriyle birlikte olduğu gibi taşınabilmeli.
Ne zaman gerekmez: bir kez çalışacak 40 satırlık bir betikte ya da bütün değişiklik isteklerinin aynı kişiden geldiği bir sınıfta. Bunları bölmek, birbirinden bağımsız değişen hiçbir şeyi ayırmaz; yalnızca dosya sayısını ve kodu okurken dosyadan dosyaya atlamayı artırır. Hızlı bir test: modülde yapılabilecek her değişiklik türünü kimin isteyeceğini yazın. Tek bir isim ya da tek bir ekip çıkıyorsa modülü olduğu gibi bırakın.
Açık/Kapalı: çalışan koda dokunmadan davranış eklemek
Açık/kapalı ilkesini Bertrand Meyer 1988'de, kalıtıma dayalı bir biçimde ortaya attı. Bugün yaygın olan yorumu 1990'larda Martin popülerleştirdi: yeni bir seçenek geldiğinde, çalışan ve test edilmiş koda dokunmadan onu ekleyebilmelisiniz.
İzin önceliği bunun için iyi bir aday, çünkü yeni izin türleri çıkabiliyor: hastane yeni bir eğitim kategorisi tanımlıyor ya da entegrasyondan yeni bir izin nedeni gelmeye başlıyor. İlk sürüm genellikle resolveLeaves içinde art arda yazılmış if bloklarıdır. TypeScript'te çoğu zaman daha temiz yol, bir veri tablosu kurup eksik satırı tip sistemine yakalatmaktır:
type LeaveType = "sick" | "annual" | "hourly" | "training";
interface Leave {
id: string;
staffId: string;
type: LeaveType;
start: Date;
end: Date;
}
// Sıralama bu örnek için uyduruldu. Gerçekte hastanenin kurallarından gelir.
const leavePolicies: Record<LeaveType, { precedence: number; blocksWholeDay: boolean }> = {
sick: { precedence: 40, blocksWholeDay: true },
training: { precedence: 30, blocksWholeDay: true },
annual: { precedence: 20, blocksWholeDay: true },
hourly: { precedence: 10, blocksWholeDay: false },
};
function stronger(a: Leave, b: Leave): Leave {
return leavePolicies[a.type].precedence >= leavePolicies[b.type].precedence ? a : b;
}
// Sadeleştirilmiş hali: kişi ve başlangıç günü başına tek anahtar
function resolveLeaves(raw: Leave[]): Leave[] {
const unique = new Map(raw.map((l) => [l.id, l] as const)); // aynı kayıt iki kez gelmiş olabilir
const perStaffDay = new Map<string, Leave>();
for (const leave of unique.values()) {
const key = `${leave.staffId}:${leave.start.toISOString().slice(0, 10)}`;
const current = perStaffDay.get(key);
perStaffDay.set(key, current ? stronger(current, leave) : leave);
}
return [...perStaffDay.values()];
}Artık "conference" gibi yeni bir izin türü eklemek, birleşim tipine (union) bir üye ve tabloya bir satır eklemek demek. Record<LeaveType, ...> o satır eklenene kadar tabloyu derletmez; resolveLeaves ise hiç değişmez. (Mükerrer personel kayıtlarını ayıklayan kod da aynı modülde durur; örnek kısa kalsın diye buraya koymadık.)
Ne zaman gerekmez: ilk seferde. Tek bir if yeterlidir; tek uygulaması olan bir strateji arayüzü, gelecek hakkında bir tahminden ibarettir. Biz ikinci ya da üçüncü seçenek gelip düzenlemeler can yakmaya başlayana kadar beklemeyi öneriyoruz. Düz bir switch bloğunun daha iyi tasarım olduğu bir durum da var: seçenekler sabittir ama üzerlerine sürekli yeni işlemler eklenir (ekrandaki etiket, çizelgedeki renk, bordro kodu). Eksiksiz (exhaustive) bir switch yazdığınızda, güncellenmesi gereken her yeri derleyici size gösterir:
function label(type: LeaveType): string {
switch (type) {
case "sick": return "Hastalık izni";
case "annual": return "Yıllık izin";
case "hourly": return "Saatlik izin";
case "training": return "Eğitim";
default: {
const unhandled: never = type; // yeni bir LeaveType eklendiğinde burada derleme hatası alırsınız
throw new Error(`Tanımsız izin türü: ${unhandled}`);
}
}
}Liskov Yerine Geçme: sözleşmeye uymak
Barbara Liskov bu fikri 1987'de bir konferans konuşmasında ortaya koydu; Jeannette Wing ile birlikte 1994'te yayımladıkları A Behavioral Notion of Subtyping makalesinde de biçimsel olarak tanımladılar. Pratikte bir arayüzün uygulaması:
- çağırandan arayüzün istediğinden fazlasını isteyemez (ön koşulu güçlendiremez),
- arayüzün söz verdiğinden azını veremez (son koşulu zayıflatamaz),
- arayüzün değişmezlerini (invariant) korumalı, üst türün izin vermediği durum değişikliklerine de kapı açmamalıdır.
İlk ikisini ters hatırlamak çok kolay. Bir uygulama arayüzün istediğinden fazlasını kabul edebilir, arayüzün garanti ettiğinden fazlasını da vaat edebilir. İhlal ters yönde olur.
Sözleşmeyi çağıranların görebileceği yere yazın:
interface LeaveSource {
/** [from, to] aralığıyla kesişen bütün izinler; aralık ne kadar uzun olursa olsun, her biri gerçek türüyle. */
fetchLeaves(from: Date, to: Date): Promise<Leave[]>;
}Şimdi bir İK yazılımının API'sinin istek başına en fazla 31 günlük aralık kabul ettiğini düşünün. Akla ilk gelen adaptör, aralık daha uzunsa hata fırlatır. Bu, ön koşulu güçlendirmektir: çizelge üreticisi üç aylık veri istediğinde, diğer bütün kaynaklarla çalışan kod bu kaynakta patlar. Sınırı adaptörün içinde çözün:
const DAY = 24 * 60 * 60 * 1000;
function* windows(from: Date, to: Date, maxDays: number): Generator<[Date, Date]> {
for (let start = from.getTime(); start < to.getTime(); start += maxDays * DAY) {
yield [new Date(start), new Date(Math.min(start + maxDays * DAY, to.getTime()))];
}
}
class VendorLeaveSource implements LeaveSource {
constructor(private readonly fetchPage: (from: Date, to: Date) => Promise<Leave[]>) {}
async fetchLeaves(from: Date, to: Date): Promise<Leave[]> {
const leaves: Leave[] = [];
for (const [start, end] of windows(from, to, 31)) {
leaves.push(...(await this.fetchPage(start, end))); // API sınırı: istek başına 31 gün
}
return leaves; // iki pencereye taşan izin iki kez gelir; kopyayı resolveLeaves atar
}
}Son koşul da aynı şekilde sessizce bozulabilir. Tanımadığı izin kodlarını "annual" olarak eşleyen bir adaptör, tip açısından kusursuz bir Leave[] döndürür ama arayüzün verdiği sözü tutmaz: izin türü artık gerçek tür değildir ve çizelge saatlik izni tam günlük izin gibi işler. Bilinmeyen kodları tahmin etmeyin; reddedin ya da bir kişinin bakması için işaretleyin.
Yedek kaynakları güvenli kılan da bu kural. EdgeHunters için yaklaşık iki buçuk yıl geliştirip işlettiğimiz gerçek zamanlı bahis oranı veri platformunda her bahis sitesinin kendi veri toplayıcısı vardı ve bir toplayıcı çöktüğünde üçüncü taraf bir oran API'si otomatik olarak devreye giriyordu. Böyle bir yedek, ancak veriyi sonradan işleyen kod verinin hangi kaynaktan geldiğini ayırt edemiyorsa güvenlidir: aynı yapı, aynı normalize edilmiş adlar.
Derleyicinin neyi yakalayıp neyi yakalamadığına dair iki not. TypeScript, metot parametrelerini strict açıkken bile iki yönlü (bivariant) kontrol eder; strict de TypeScript 6.0'dan bu yana varsayılan olarak açık. Bu yüzden bir uygulama kabul ettiği girdiyi sessizce daraltabilir:
type Recipient = { email: string } | { phone: string };
interface Notifier {
send(to: Recipient, text: string): Promise<void>;
}
class SmsNotifier implements Notifier {
// "strict": true ile derlenir. Elinde Notifier tutan biri yine de { email } gönderebilir
// ve bu satır çalışma zamanında patlar.
async send(to: { phone: string }, text: string): Promise<void> {
console.log(`SMS: ${to.phone.trim()}: ${text}`);
}
}Üyeyi metot yerine özellik olarak tanımlarsanız (send: (to: Recipient, text: string) => Promise<void>) strictFunctionTypes devreye girer ve daraltılmış uygulama reddedilir. C# ve Java'da ise daha dar parametre tipli bir metot aşırı yükleme (overload) olur; sınıf arayüzü uygulamamış olacağı için derlenmez. Bu dillerde yerine geçme sorunları çalışma zamanında, çoğunlukla hata fırlatan bir metot olarak çıkar karşınıza. .NET bunu bilerek yapar: bir diziyi IList arayüzüne çevirip Add çağırırsanız, diziler sabit boyutlu olduğu için her zaman NotSupportedException alırsınız. Java'da List.of ile oluşturulan listeler de add çağrısında UnsupportedOperationException fırlatır. İkisi de belgelenmiş davranış, ama bir IList ya da List alan kodun, nesnenin nereden geldiğini bilmeden eleman eklemeye güvenememesinin nedeni de bu.
Arayüz Ayrımı: her istemciye kendi küçük arayüzü
İstemciler kullanmadıkları metotlara bağımlı olmamalı. Bir hastane sisteminin yapabildiği her şeyi tek bir arayüzde topladığımızı düşünelim:
type Staff = { id: string; name: string }; // kısaltılmış
type HoursRow = { staffId: string; date: string; hours: number }; // kısaltılmış
interface HrSystem {
fetchStaff(): Promise<Staff[]>;
fetchLeaves(from: Date, to: Date): Promise<Leave[]>;
pushWorkedHours(rows: HoursRow[]): Promise<void>; // yalnızca bordro aktarımı kullanıyor
}Çizelge üreticisinin ihtiyacı personel listesi ve izinler. Çalışılan saatleri yalnızca bordro aktarımı gönderiyor. Tek arayüzle, saat verisi kabul etmeyen bir sistemin entegrasyonu bile pushWorkedHours metodunu yazmak zorunda kalır; bu da genellikle hata fırlatan bir metot olur, yani bir önceki bölümdeki yerine geçme sorunu. Üstelik testte kullandığınız her sahte nesnenin (fake), tek bir metodu denemek için üç metodu birden taklit etmesi gerekir.
Arayüzü istemcilere göre bölün:
interface StaffDirectory { fetchStaff(): Promise<Staff[]> }
interface LeaveSource { fetchLeaves(from: Date, to: Date): Promise<Leave[]> }
interface HoursSink { pushWorkedHours(rows: HoursRow[]): Promise<void> }
async function buildRosterInput(hr: StaffDirectory & LeaveSource, from: Date, to: Date) {
const [staff, leaves] = await Promise.all([hr.fetchStaff(), hr.fetchLeaves(from, to)]);
return { staff, leaves: resolveLeaves(leaves) };
}Tek bir adaptör sınıfı yine de üçünü birden uygulayabilir. TypeScript'in yapısal tip sistemi bunu kolaylaştırır: fonksiyon yalnızca kullandığı üyeleri ister (yukarıdaki gibi bir kesişim tipiyle ya da Pick<HrSystem, "fetchLeaves"> ile) ve bu üyelere sahip her nesne, iki satırlık bir sahte nesne de dahil, parametre olarak verilebilir.
Aynı düşünce HTTP API'leri için de geçerli. Bir ekran hepsine ihtiyaç duyduğu için 40 alan döndüren bir endpoint, diğer bütün istemcileri de o 40 alana bağımlı hale getirir.
Ne zaman gerekmez: tek istemci ve tek uygulama varsa. Başka kimsenin kullanmadığı bir arayüzü bölmek boşuna uğraştır.
Bağımlılıkların Tersine Çevrilmesi: arayüzün sahibi iş kuralıdır
Üst seviye iş kuralları alt seviye ayrıntılara bağlı olmamalı. İkisi de soyutlamalara bağlı olmalı ve soyutlama iş kuralı tarafına ait olmalı. İzin örneğinde LeaveSource arayüzünü izinleri içeri alan kod (importLeaves) tanımlıyor, her hastanenin adaptörü de bu arayüzü uyguluyor. Böylece İK yazılımının istemci kütüphanesini yalnızca tek bir dosya import ediyor.
Parçaları birbirine bağlamanın olağan yolu bağımlılık enjeksiyonu (dependency injection): somut uygulamayı constructor ya da parametre üzerinden içeri verirsiniz. Bir DI container kullanmak zorunlu değil; uygulamanın başlangıç kodundaki birkaç satır aynı işi görür.
Karşılığını bir bağımlılık sorun çıkardığında alırsınız. Kanada'daki bir petrol sahası hizmetleri şirketi için kurduğumuz saha fişi otomasyonu, günde yaklaşık 100 belgeyi bir LLM ile okuyor. Canlıda sorunların çoğu LLM API'sinin istek sınırlarından (rate limit, HTTP 429) çıktı; çözüm, alternatif modellere geçen yedek yollar eklemek oldu. O proje bir n8n iş akışı, dolayısıyla yedek yol akışın içinde bir dal. Uygulama kodunda da fişleri işleyen kod kendi tanımladığı bir arayüze bağlıysa aynı değişiklik küçük kalır:
interface FieldTicket { ticketNumber: string; customer: string; hours: number } // kısaltılmış
interface Extractor {
extract(pdf: Uint8Array): Promise<FieldTicket>;
}
class RateLimitError extends Error {}
class FallbackExtractor implements Extractor {
constructor(private readonly chain: Extractor[]) {}
async extract(pdf: Uint8Array): Promise<FieldTicket> {
let lastError: unknown = new Error("Tanımlı extractor yok");
for (const extractor of this.chain) {
try {
return await extractor.extract(pdf);
} catch (err) {
if (!(err instanceof RateLimitError)) throw err; // gerçek hataları yeniden denemenin arkasına saklamayın
lastError = err;
}
}
throw lastError;
}
}
// Somut modelleri yalnızca başlangıç kodu bilir:
// const extractor = new FallbackExtractor([primaryModel, secondaryModel]);Fiş işleyen kod, bir yedek olduğundan hiç haberdar olmaz. FallbackExtractor da bir Extractor olduğundan bu küçük sınıf açık/kapalı ilkesini (fiş işleyen koda dokunmadan yeni davranış) ve yerine geçme ilkesini (aynı sözleşmeye uyduğu için) de gösteriyor.
SOLID'in fazla geldiği yerler
Her ilkenin refleksle uygulandığında ortaya çıkan bir bozulma biçimi var:
- Her sınıf için bir arayüz ve her arayüzün sonsuza kadar tek bir uygulaması (
IUserServiceileUserServiceImplyan yana). Hiçbir şey kazandırmadan bir dosya daha ekler, kodu okuyanı bir dosyadan ötekine bir kez daha gönderir. Arayüzleri gerçek sınırlar için saklayın: I/O, dış servisler, sistem saati, testte taklit etmeniz gereken her şey. - Sorumlulukların o kadar ince bölünmesi ki tek bir özellik dokuz dosyaya dokunuyor. Martin'in SRP özeti iki yönde de işler: aynı nedenle değişen şeyleri bir araya toplayın. İki sınıf her zaman birlikte değişiyorsa büyük ihtimalle aynı yerde durmaları gerekir.
- Kimsenin istemediği seçenekler için genişleme noktaları. Her biri okunacak, test edilecek ve uyumlu tutulacak ek koddur.
SOLID'in hâlâ doğru çerçeve olduğunu herkes kabul etmiyor. Davranış odaklı geliştirmeyi (BDD) ortaya atan Dan North, 2022'de bu ilkeleri artık faydalı bulmadığını yazdı ve yerine CUPID'i önerdi: kurallar yerine iyi kodun özellikleri (composable, Unix philosophy, predictable, idiomatic, domain-based; yani birleştirilebilir, Unix felsefesine uygun, öngörülebilir, dilin alışkanlıklarına uygun ve alana dayalı). North'un, SRP'ye göre bölmenin yapay ayrım çizgileri yaratabileceği uyarısı akılda tutulmaya değer.
Bizim görüşümüz şu: SRP ve DIP, yıllarca yaşayacak hemen her kod tabanında harcanan emeği fazlasıyla geri öder; çoğunlukla da önemli mantığı test edilebilir kıldıkları için. LSP bir doğruluk kuralıdır; ihlal ederseniz, tarzınız ne olursa olsun, hata alırsınız. OCP ve ISP'yi ise geç uygulamak daha doğru: ikinci seçenek ya da ikinci istemci gerçekten ortaya çıktığında.
Var olan koda SOLID uygulamak
Bütün kod tabanını SOLID'e göre yeniden düzenlemeye kalkmayın. Değişikliğin canınızı yaktığı yerden başlayın.
- Önce en sık değişen dosyaları bulun. Kötü yapı size en pahalıya orada patlar:
git log --since="6 months ago" --name-only --pretty=format: | grep -v '^$' | sort | uniq -c | sort -rn | head -20- Kodu yerinden oynatmadan önce bugünkü davranışı testlerle sabitleyin. Hiç test yoksa, mevcut çıktıyı olduğu gibi kaydeden karakterizasyon testleri başlamak için yeterli; bu alışkanlığı test-driven geliştirme yazımızda anlattık.
- I/O da yapan sınıfların içindeki saf mantığı dışarı çıkarın (SRP). Genellikle en az riskle en büyük kazancı bu adım verir.
- Her dış servisle kodunuz arasına kendi tanımladığınız bir arayüz koyun (DIP); böylece bir sonraki API değişikliği ya da kesinti yalnızca tek bir adaptörü etkiler.
- Sahte nesne yazmak can sıkmaya başladığında şişkin arayüzü bölün (ISP); her sprintte elden geçen bir
switchbloğunu da bir tabloya çevirin (OCP).
Kod tabanınızda her değişiklik ilgisiz bir yeri bozuyorsa, bu, bakım ve destek kapsamında üstlendiğimiz türden bir iş; daha büyük yeniden yazımlar için legacy sistem modernizasyonu yazımıza bakabilirsiniz.
Sık sorulan sorular
SOLID prensiplerini kim ortaya koydu?
Seti Robert C. Martin bir araya getirdi ve büyük kısmını 2000 tarihli Design Principles and Design Patterns makalesinde anlattı. Fikirlerden ikisi daha eski: açık/kapalı ilkesi Bertrand Meyer'den, yerine geçme ilkesi Barbara Liskov'dan geliyor. Martin'in anlattığına göre Michael Feathers 2004 civarında ona bir e-posta atıp baş harfler yeniden sıralanınca SOLID kelimesinin çıktığını söylemiş.
SOLID prensipleri hâlâ geçerli mi?
Yıllarca bakımı yapılacak nesne yönelimli ve TypeScript kodu için evet; yeter ki ilkeleri kontrol listesi yerine tasarım hakkında soru sormak için kullanın. Küçük betiklerde ve ana yapıyı sizin kontrol etmediğiniz bir framework'ün belirlediği kodda önemleri azalır.
SOLID nesne yönelimli olmayan kodda da işe yarar mı?
Fikirler işe yarar, yalnızca başka adlarla anılırlar. Go'da bir tipin bir arayüzü uyguladığını ayrıca belirtmeniz gerekmez, Python'da da duck typing ve typing.Protocol var; bu yüzden iki dilde de arayüz ayrımı ve bağımlılıkların tersine çevrilmesi az zahmetlidir. Fonksiyonel kodda bir fonksiyonu parametre olarak geçmek bağımlılıkların tersine çevrilmesidir, tek bir değişme nedeni olan küçük ve saf bir fonksiyon da tek sorumluluktur.
Mülakatta SOLID nasıl anlatılır?
Beş ilkeyi sayın, ardından her biri için üzerinde çalıştığınız koddan bir örnek verin ve hangi durumda uygulamayacağınızı söyleyin. Yerine geçme kuralını net ifade edin: ön koşul güçlendirilmez, son koşul zayıflatılmaz. Kendi işinizden bir örnek, Kare ile Dikdörtgen örneğinden çok daha akılda kalıcıdır.
İlgili Makaleler
Redis Nedir, Ne İşe Yarar? Kullanım Alanları ve Doğru Ayarlar
Redis'in ne olduğu, hangi işlere uygun olduğu ve her işin hangi ayarı gerektirdiği: önbellekten Celery kuyruğuna, oturumdan kilide. Redis 8 ile Valkey farkı da yazıda.
Devamını Oku: Redis Nedir, Ne İşe Yarar? Kullanım Alanları ve Doğru AyarlarMLOps Rehberi: Makine Öğrenmesi Modellerini Production'a Taşıma
Giriş: MLOps Nedir ve Neden Önemlidir? Makine öğrenmesi modelleri geliştirmek günümüzde nispeten kolaydır. Açık kaynak kütüphaneleri kullanarak son derece başarılı modeller oluşturabiliriz. Ancak bu modelleri production ortamına taşıyarak, ölçeklendirebilir, güvenilir ve sürdürülebilir şekilde çalıştırmak tamamen farklı bir hikayedir. Araştırmalara göre, veri bilimcileri tarafından geliştirilen makine öğrenmesi modellerinin %87'si hiçbir zaman production ortamına ulaşmaz. Bu başarısızlı
Devamını Oku: MLOps Rehberi: Makine Öğrenmesi Modellerini Production'a TaşımaBilgisayarlı Görü Uygulamaları: Nesne Tespiti, OCR ve Endüstriyel AI
Bilgisayarlı Görü Uygulamaları: Endüstriyel ve Medikal AI'nin Temel Teknolojisi Bilgisayarlı görü, makine öğrenmesinin en etkili alanlarından biridir. Türkiye'de yaşanan dijital dönüşüm sürecinde, özellikle üretim, sağlık ve lojistik sektörlerinde görü tabanlı otomasyon kritik hale gelmiştir. Smart Maple olarak Ankara'da geliştirdiğimiz çözümler, son beş yılda 150+ kuruluşunun üretim verimliliğini ortalama %35 oranında artırmıştır. Bu rehberde, bilgisayarlı görü teknolojisinin iş değeri
Devamını Oku: Bilgisayarlı Görü Uygulamaları: Nesne Tespiti, OCR ve Endüstriyel AI
