Obje yönelimli programlama ile daha temiz kod yazmanın 9 kuralı, gerçek dünya örnekleri ile. Teoriden uzak, pratik bilgiler.
Object Calisthenics, Jeff Bay tarafından oluşturulan 9 kuraldan oluşur. Bu kurallar 2008 yılında yayımlanan ThoughtWorks Anthology kitabında yer almaktadır. Amaç basit: Bu kuralları disiplinli bir alıştırma olarak uygulamak, sizi yüksek uyum, düşük bağlılık ve yalnızca tek bir sorumluluğa sahip kod yazmaya zorlar — bunu yaparken bile SOLID prensiplerini düşünmeden.
İsim, gereksiz soyutlamaları ortadan kaldıran kalistenik egzersizlerden gelmektedir. Gereksiz soyutlamaları kaldırarak uygun tasarım içgüdüsünü geliştirmek için bu kısıtları uyguluyoruz.
Aşağıdaki örnekler, kiralama sözleşmeleri yönetimi alanını kullanmaktadır — kredi analizi, teminatlar, tahsilatlar, bildirimler gibi. Hiçbir yerde Foo veya Bar yok.
9 Kural
| # | Kural |
|---|---|
| 01 | Her metot için bir seviye girinti |
| 02 | else kullanmayın |
| 03 | Primitifler ve stringleri kapsayın |
| 04 | Birinci sınıf koleksiyonlar |
| 05 | Her satırda bir nokta |
| 06 | İsimleri kısaltmayın |
| 07 | Küçük varlıklar tutun |
| 08 | En fazla 2 nesne değişkeni |
| 09 | Getter ve setter kullanmayın |
01 — Her Metot için Bir Seviye Girinti
Eğer bir metotta bir for yapısı bir if içinde yer alıyorsa, bu metot çok fazla iş yapıyor demektir. Her bir bloğun içeriğini, niyeti tanımlayan bir özel metoda çıkartın.
❌ Kötü
public function processOverdueContracts(Collection $contracts): void
{
foreach ($contracts as $contract) {
if ($contract->isOverdue()) {
foreach ($contract->payments() as $payment) {
if ($payment->isPending()) {
$payment->markAsLate(); // 3 seviye girinti
}
}
}
}
}
✅ İyi
public function processOverdueContracts(Collection $contracts): void
{
$contracts
->filter(fn($c) => $c->isOverdue())
->each($this->markPendingPaymentsAsLate(...));
}
private function markPendingPaymentsAsLate(Contract $contract): void
{
$contract
->payments()
->filter(fn($p) => $p->isPending())
->each(fn($p) => $p->markAsLate());
}
Niçin bu çalışır: Her metot, izole bir sorumluluğa sahiptir ve ayrı ayrı test edilebilir. markPendingPaymentsAsLate ismi, yorum yazmadan niyeti açıklar.
02 — else Kullanmayın
Erken dönüş (early return) kullanın: önce geçersiz durumları kontrol edin ve geri dönün. “Mutlu yol” en sonunda kalır, iç içe yapılar olmadan. Bu, günlük yaşamda hemen en büyük etkisi olan kuraldır.
❌ Kötü
public function approve(Application $app): ApprovalResult
{
if ($app->hasValidDocuments()) {
if ($app->creditScore() >= 600) {
if (!$app->hasActiveDenial()) {
return ApprovalResult::approved();
} else {
return ApprovalResult::denied('Aktif engel var');
}
} else {
return ApprovalResult::denied('Yetersiz kredi puanı');
}
} else {
return ApprovalResult::denied('Geçersiz belgeler');
}
}
✅ İyi
public function approve(Application $app): ApprovalResult
{
if (!$app->hasValidDocuments()) {
return ApprovalResult::denied('Geçersiz belgeler');
}
if ($app->creditScore() < 600) {
return ApprovalResult::denied('Yetersiz kredi puanı');
}
if ($app->hasActiveDenial()) {
return ApprovalResult::denied('Aktif engel var');
}
return ApprovalResult::approved();
}
Niçin bu çalışır: Liste bir kontrol listesi gibi okunabilir. Her koruma, geçersiz bir durumu ortadan kaldırır. Mutlu yol en sonunda açıkça görünür. Sıfır iç içe yapı, yeni doğrulamalar eklemek kolaydır.
03 — Primitifler ve Stringleri Kapsayın
Bağımsız stringler, tam sayılar ve float’lar iş kurallarını taşıyamaz. Bir Value Object geçersiz bir verinin (örneğin, geçersiz bir CPF) sisteme girmesini engeller — validasyon tek bir yerde, yapıcıda (constructor) gerçekleşir, 20 yerden dağılmaz.
❌ Kötü
// Kontrolcü, Servis, İş... her biri kendi doğrulama yöntemini kullanıyor
$cpf = $request->input('cpf'); // herhangi bir string
$rent = $request->input('rent_value'); // float, garantisi yok
if (strlen(preg_replace('/\D/', '', $cpf)) !== 11) {
throw new InvalidArgumentException('Geçersiz CPF');
}
if ($rent 0) {
throw new InvalidArgumentException('Geçersiz değer');
}
✅ İyi
final class Cpf
{
private readonly string $value;
public function __construct(string $raw)
{
$digits = preg_replace('/\D/', '', $raw);
CpfValidator::ensureValid($digits); // Geçersizse istisna atar
$this->value = $digits;
}
public function formatted(): string
{
return preg_replace(
'/(\d{3})(\d{3})(\d{3})(\d{2})/',
'$1.$2.$3-$4',
$this->value
);
}
}
final class Money
{
public function __construct(
private readonly int $cents // her zaman kuruş olarak
) {
if ($this->cents 0) {
throw new DomainException('Negatif para olamaz');
}
}
public function times(int $multiplier): self
{
return new self($this->cents * $multiplier);
}
public function isLessThan(self $other): bool
{
return $this->cents $other->cents;
}
}
// Kontrolcüde: tip koruması bütünlüğü sağlar
$cpf = new Cpf($request->input('cpf'));
$rent = Money::fromFloat($request->input('rent_value'));
Niçin bu çalışır: Geçersiz bir Cpf nesnesi oluşturmak imkansızdır. Validasyon tek bir yerde bulunur. Tip, niyeti açıklar — Money tipi float tipinden çok daha anlamlıdır.
04 — Birinci Sınıf Koleksiyonlar
Bir sınıf bir koleksiyonu içeriyorsa, başka hiçbir niteliği olmamalıdır. Dizileri ve koleksiyonları kendi sınıflarında, alan davranışı ile kapsüllerin. Böylece, filtreleme mantığını tüm servisler arasında yaymaktan kaçınılır.
❌ Kötü
// ReportService.php
$overdueTotal = Contract::all()
->filter(fn($c) => $c->status === 'overdue')
->sum('value');
// DashboardService.php — aynı mantık tekrarlandı
$overdueByAgency = Contract::all()
->filter(fn($c) => $c->status === 'overdue')
->groupBy('agency_id');
✅ İyi
final class ContractCollection
{
public function __construct(
private readonly Collection $items
) {}
public function overdue(): self
{
return new self(
$this->items->filter(fn($c) => $c->isOverdue())
);
}
public function totalValue(): Money
{
return Money::fromFloat($this->items->sum('value'));
}
public function groupedByAgency(): Collection
{
return $this->items->groupBy('agency_id');
}
}
// Kullanım — anlamlı ve yeniden kullanılabilir
$contracts = new ContractCollection(Contract::all());
$overdueTotal = $contracts->overdue()->totalValue();
$overdueByAgency = $contracts->overdue()->groupedByAgency();
Niçin bu çalışır: “overdue” tanımı sadece bir yerde bulunur. Eğer kural değişirse, overdue() metodunu değiştirirsiniz ve tüm tüketiciler güncellenir.
05 — Her Satırda Bir Nokta
Demeter Yasası: bir nesne yalnızca komşuları ile konuşmalıdır, arkadaşlarıyla değil. Bir nesnenin iç yapısında gezinmek görünmeyen bağlılık yaratır — eğer Contact değişirse, $contract->tenant()->contact()->email() erişen her şey bozulur.
❌ Kötü
// 4 bağımlılık bir satırda
$email = $contract
->tenant()
->contact()
->primaryEmail()
->address();
$this->mailer->to($email)->send(new DueDateMail());
✅ İyi
// Contract.php — yalnızca gerekenleri açığa çıkarın
class Contract
{
public function tenantEmail(): string
{
return $this->tenant()->primaryEmail();
}
}
// NotificationJob.php — yalnızca bir nokta
$this->mailer
->to($contract->tenantEmail())
->send(new DueDateMail($contract));
Geçerli bir istisna: Laravel Query Builder ve Collections, zincirleme için tasarlanmıştır.
Contract::query()->where(...)->with(...)->get()Flent Interface kullanımıdır. Kural, iç yapıların gezinilmesine uygulanır, builder’lara uygulanmaz.
06 — İsimleri Kısaltmayın
Kod, yazıldığından çok daha fazla okunur. Kısaltmalar, yazımda birkaç saniye kazandırır ancak her okumada dakikalar kaybettirir. Eğer bir isim anlamak için bağlam gerektiriyorsa, yanlış bir isimdir.
❌ Kötü
public function upd(UpdCtrReq $req, int $id)
{
$ctr = Ctr::findOrFail($id);
$svc = new CtrSvc();
$res = $svc->upd($ctr, $req->validated());
return $res
Kaynak: Orijinal Makale


